Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Star projection (*) is a type projection syntax in Kotlin used to represent an unknown type argument within a generic type. It acts as a type-safe equivalent to Java’s unbounded wildcards (<?>), allowing developers to reference a generic type without specifying its exact type parameter, while the compiler strictly enforces type safety based on the parameter’s declaration-site variance. When a generic type is instantiated with a star projection, the Kotlin compiler automatically resolves the allowed read and write operations by mapping the * to specific in (contravariant) and out (covariant) projections.

Runtime Type Checking and Type Erasure

A fundamental application of star projections is performing runtime type checks. Because Kotlin erases generic type arguments at runtime (type erasure), it is impossible to check if an object is an instance of a generic type with a specific type argument. Attempting to do so results in a compiler error. Star projections provide the required mechanism to safely check the base type at runtime.
fun checkType(obj: Any) {
    // if (obj is List<String>) { ... } // Compilation error: Cannot check for instance of erased type
    
    if (obj is List<*>) {
        // Allowed: Checks if obj is a List of some unknown type
        println("Object is a List of size ${obj.size}")
    }
}

Variance Resolution Rules

The behavior of a star-projected type depends entirely on how the generic type parameter was originally declared.

1. Covariant Declaration (out T)

If a type parameter is declared as covariant, the star projection allows reading the upper bound of T, but prohibits writing.
  • Declaration: interface Producer<out T>
  • Star Projection: Producer<*>
  • Compiler Interpretation: Producer<out Any?>
  • Behavior: You can safely read values of type Any? from the object, but you cannot pass any values to methods that expect T.
interface Producer<out T> {
    fun produce(): T
}

fun testProducer(producer: Producer<*>) {
    val item: Any? = producer.produce() // Allowed: Reads as Any?
}

2. Contravariant Declaration (in T)

If a type parameter is declared as contravariant, the star projection prevents writing any value to the object, as the exact accepted type is unknown.
  • Declaration: interface Consumer<in T>
  • Star Projection: Consumer<*>
  • Compiler Interpretation: Consumer<in Nothing>
  • Behavior: You cannot safely write any value to methods expecting T (because the Nothing type has no instances).
interface Consumer<in T> {
    fun consume(item: T)
}

fun testConsumer(consumer: Consumer<*>) {
    // consumer.consume("test") // Compilation error: Expected Nothing
}

3. Invariant Declaration (T)

If a type parameter is invariant (neither in nor out), the star projection splits the behavior for reading and writing.
  • Declaration: interface MutableBox<T>
  • Star Projection: MutableBox<*>
  • Compiler Interpretation: MutableBox<out Any?> for reading, MutableBox<in Nothing> for writing.
  • Behavior: You can read values as Any?, but you cannot write any values.
interface MutableBox<T> {
    fun get(): T
    fun set(value: T)
}

fun testBox(box: MutableBox<*>) {
    val item: Any? = box.get() // Allowed: Reads as Any?
    // box.set("test") // Compilation error: Expected Nothing
}

4. Type Parameters with Upper Bounds

If the generic type parameter has a defined upper bound other than Any?, the star projection respects that bound during covariant reads.
  • Declaration: interface BoundBox<T : CharSequence>
  • Star Projection: BoundBox<*>
  • Compiler Interpretation: BoundBox<out CharSequence> for reading, BoundBox<in Nothing> for writing.
interface BoundBox<T : CharSequence> {
    fun get(): T
}

fun testBoundBox(boundBox: BoundBox<*>) {
    val item: CharSequence = boundBox.get() // Allowed: Reads as CharSequence
}

Multiple Type Parameters

For types with multiple generic parameters, each parameter can be star-projected independently. The compiler resolves each * according to its specific declaration.
interface Function<in T, out U> {
    fun apply(arg: T): U
}

fun testFunctions(
    f1: Function<*, String>, // Resolves to Function<in Nothing, String>
    f2: Function<Int, *>,    // Resolves to Function<Int, out Any?>
    f3: Function<*, *>       // Resolves to Function<in Nothing, out Any?>
) {
    // Implementation omitted
}
Master Kotlin with Deep Grasping Methodology!Learn More