The asterisk ( *) in Kotlin generics denotes a star projection, which is a type-safe way to work with a generic type when the specific type argument is unknown or irrelevant. It's a key feature of Kotlin's type system that provides flexibility and improves code readability without sacrificing safety, unlike Java's raw types.
What is a star projection?
A star projection is a special kind of use-site variance. While Kotlin's in and out keywords declare variance at the class level (declaration-site), a star projection allows a "wildcard-like" behavior at the point of usage. It means, "This is a generic type, but for this specific operation, the type parameter doesn't matter".
The compiler replaces the star (*) with a safe, inferred projection based on the generic type's variance:
- For a covariant type
Foo<out T>:Foo<*>becomesFoo<out Any?>. This allows the reading of values, but since the exact subtype is unknown, the compiler projects it asAny?. - For a contravariant type
Foo<in T>:Foo<*>becomesFoo<in Nothing>. This allows the writing of values, butNothinghas no instances, so nothing can be passed into it in a type-safe manner. - For an invariant type
Foo<T>:Foo<*>becomesFoo<out Any?>for reading andFoo<in Nothing>for writing. This provides read-only access for an unknown type and prevents writes.
Star projection vs. Java wildcards
Star projections are Kotlin's safer, more concise alternative to Java's wildcard types (<?>).
- Conciseness: The star projection syntax (
List<*>) is more succinct than Java's unbounded wildcard (List<?>). - Safety: Kotlin’s null safety is built into star projections, as a star projection of a nullable generic type parameter includes
?. Java's wildcards are prone to null-related issues. - Intent: A star projection clearly signals that the type parameter is unknown or unimportant in the given context, allowing for limited, type-safe operations.
Common use cases
1. Functions that don't care about the generic type
This is the most common use case. If a function operates on a generic collection but only needs information that doesn't depend on the specific type, a star projection is perfect.
fun printListSize(list: List<*>) {
println("List size: ${list.size}")
}
fun main() {
val intList: List<Int> = listOf(1, 2, 3)
val stringList: List<String> = listOf("a", "b", "c")
printListSize(intList) // Output: List size: 3
printListSize(stringList) // Output: List size: 3
}
Use code with caution.
In this example, the printListSize function only needs to know the list's size, not its element types. List<*> makes the function flexible and type-safe.
2. Working with heterogeneous data
When there is a collection of items with different but related types, star projection is useful for reading the data generically.
interface Event
data class LoginEvent(val userId: String) : Event
data class PurchaseEvent(val itemId: Int) : Event
fun processEvents(events: List<*>) {
events")
} else if (event is PurchaseEvent) {
println("Item purchased: ${event.itemId}")
}
}
}
fun main() {
val events = listOf(LoginEvent("user123"), PurchaseEvent(456))
processEvents(events)
}
Use code with caution.
Here, List<*> represents a list of unknown Event subtypes. Smart casts can then be used to check the runtime type of each element and process it accordingly.
3. Interacting with Java wildcards
When interfacing with Java code that uses wildcards like List<?>, Kotlin automatically treats it as a star projection (List<*>) to maintain type safety across languages.
4. Type-safe reading from invariant types
For a mutable, invariant generic class like MutableList<T>, a star projection allows it to be used in a read-only context safely.
fun printFirstElement(list: MutableList<*>) {
val firstElement: Any? = list.firstOrNull()
println(firstElement)
// list.add("new item") // ❌ Compile error: The type is unknown.
}
Use code with caution.
Even though MutableList is designed for reading and writing, MutableList<*> restricts it to safe, read-only operations for the purpose of this function.
Restrictions and limitations
- No writing: Elements cannot be added to a collection with a star-projected type, as the compiler doesn't know the element type it should accept.
- Loss of type information: While elements can be read, they will be treated as the upper bound of the generic type, which is
Any?unless specified otherwise. If specific type information is needed, a type check (isoras) must be performed. - Context is key: Star projections are not a substitute for
AnyorAny?. They are specifically for generics when the type parameter is unknown, whereasAnyis the base type for all Kotlin classes.
Summary
| Feature | Star Projection (*) |
Concrete Type (T) |
Any / Any? |
|---|---|---|---|
| Use Case | Unknown type parameter | Specific, known type parameter | Base type for any object |
| Type Safety | Type-safe read/write projection | Full type safety | Type safety requires casting |
Reads (out) |
Any? (or upper bound) |
T |
Any / Any? |
Writes (in) |
Nothing (in effect, none) |
T |
Any / Any? (non-generic) |
| Example | List<*> |
List<String> |
List<Any> |
The star projection is a powerful tool for writing flexible, reusable, and type-safe generic code in Kotlin, particularly for scenarios where the exact type parameter isn't needed for a specific operation. It allows you to express your intent clearly, relying on the compiler to enforce safety rules and prevent unintended side effects.