Question

Kotlin Generics: Difference Between `*` and `Any`

kotlingenerics

Question

In Kotlin generics, what is the difference between SomeGeneric<*> and SomeGeneric<Any>?

I understand * as a wildcard that can represent any type, and Any as the root type that all non-null objects inherit from. Because of that, they seem similar at first glance. Are they actually the same, or do they behave differently?

For example:

class SomeGeneric<T>(val value: T)

How should I think about these two types?

SomeGeneric<*>
SomeGeneric<Any>

Short Answer

By the end of this page, you will understand why SomeGeneric<*> and SomeGeneric<Any> are not the same in Kotlin. You will learn what star projection means, how it differs from explicitly using Any, and how variance affects what you can safely read from or write to a generic type.

Concept

In Kotlin, SomeGeneric<Any> means a generic type whose type argument is exactly Any.

SomeGeneric<*> means a generic type whose type argument is unknown.

That difference is very important.

SomeGeneric<Any>

When you write:

SomeGeneric<Any>

you are saying:

  • this generic container holds values of type Any
  • the type parameter is known and fixed as Any
  • this is not the same as “any possible generic type argument”

For example, if you have SomeGeneric<String>, that is usually not assignable to SomeGeneric<Any> because generic types in Kotlin are invariant by default.

SomeGeneric<*>

When you write:

SomeGeneric<*>

you are saying:

  • this is a SomeGeneric of some type, but I do not know which one
  • the actual type argument might be String, Int, User, or anything else
  • because the type is unknown, Kotlin restricts what you can safely do with it

This is called a star projection.

Why this matters

This distinction matters because reading and writing become different when the type argument is unknown.

  • If the type is Any, you know what type the container expects.
  • If the type is *, you do not know the real type argument, so Kotlin protects you from unsafe operations.

A key idea

Think of these two types like this:

  • SomeGeneric<Any> = “a box specifically for Any values”
  • SomeGeneric<*> = “a box of some unknown type”

Those are not equivalent.

Nullability note

Also remember:

  • Any means any non-null object
  • Any? includes null

So SomeGeneric<Any> is also different from SomeGeneric<Any?>.

Mental Model

Imagine labeled boxes.

  • SomeGeneric<Any> is a box labeled: "This box stores Any values."
  • SomeGeneric<*> is a box labeled: "This box stores some type, but the label is hidden."

If the label says Any, you know what kind of values are allowed.

If the label is hidden, you can look at the box carefully, but you cannot safely put just any value into it, because the real hidden label might actually be String, Int, or something else.

So:

  • Any is a known type
  • * is an unknown type argument

That is the mental shift that makes Kotlin generics easier to understand.

Take Quiz

Syntax and Examples

Here is a simple generic class:

class Box<T>(var value: T)

Example 1: Box<Any>

val box: Box<Any> = Box("hello")
box.value = 42
box.value = true

This works because Box<Any> can store any non-null value.

Example 2: Box<String> is not Box<Any>

val stringBox: Box<String> = Box("hello")

// Does not compile
// val anyBox: Box<Any> = stringBox

Why not? Because if Kotlin allowed this, you could do:

val stringBox: Box<String> = Box("hello")
// val anyBox: Box<Any> = stringBox
// anyBox.value = 123

Now a Box<String> would contain an Int, which breaks type safety.

Example 3: Box<*>

val stringBox: Box<String> = Box("hello")
val unknownBox: Box<*> = stringBox

This works because Box<*> means “a Box of some unknown type.”

You can safely read from it, but what you get is limited:

val value = unknownBox.value
println(value)

The type of value is treated safely by Kotlin because the real type argument is unknown.

Example 4: Why writing is restricted

class MutableBox<T>(var value: T)

val stringBox: MutableBox<String> = MutableBox("hello")
val unknownBox: MutableBox<*> = stringBox

// Does not compile
// unknownBox.value = "new value"

Kotlin blocks that assignment because unknownBox might actually be a MutableBox<Int>, MutableBox<Boolean>, or some other type.

Example 5: Read-only generic types

If a class is declared with covariance using out, things change:

class Producer<out T>(private val value: T) {
    fun get(): T = value
}

val producer: Producer<String> = Producer("hello")
val anyProducer: Producer<Any> = producer

This works because Producer only produces values and never consumes them.

So the difference between * and Any also connects to variance.

Step by Step Execution

Consider this code:

class Box<T>(var value: T)

fun main() {
    val original: Box<String> = Box("Kotlin")
    val unknown: Box<*> = original

    val result = unknown.value
    println(result)
}

Step 1: Create original

val original: Box<String> = Box("Kotlin")
  • original is a Box<String>
  • its value is the string "Kotlin"

Step 2: Assign to Box<*>

val unknown: Box<*> = original
  • this is allowed
  • Kotlin treats unknown as “a Box of some unknown type”
  • the real type is still String, but through this reference, that information is hidden

Step 3: Read the value

val result = unknown.value
  • reading is safe
  • Kotlin allows it because the box definitely contains some value
  • but because the exact type is unknown from this reference, Kotlin gives you a safely restricted type view

Step 4: Print the value

println(result)
  • output will be:
Kotlin

What you cannot do

// unknown.value = "New"

This is not allowed because Kotlin cannot guarantee that the hidden type argument accepts a String.

If unknown were really a Box<Int>, that assignment would be unsafe.

Real World Use Cases

Star projections are useful when you need to work with generic objects but do not care about their exact type parameter.

1. Logging and debugging

You may want to print or inspect a generic container without needing to know its exact type.

fun printBox(box: Box<*>) {
    println(box.value)
}

2. Framework and library code

Libraries often receive values whose generic types are not known in advance.

Examples:

  • serializers
  • dependency injection containers
  • reflection utilities
  • UI binding code

3. Working with collections of unknown element type

fun printList(list: List<*>) {
    for (item in list) {
        println(item)
    }
}

This is common when writing reusable helper functions.

4. API boundaries

Sometimes an API only needs to know that a generic object exists, not what specific type it contains.

In that case, * is often better than forcing Any.

5. Safer read-only access

If you only need to read values and avoid writes, star projection communicates that intention clearly.

Take Quiz

Real Codebase Usage

In real projects, developers use * and Any for different purposes.

Use SomeGeneric<*> when the type argument is unknown

This is common in utility code.

fun logValue(box: Box<*>) {
    println("Value: ${box.value}")
}

Pattern:

  • inspect values
  • pass generic objects around without caring about the exact type
  • avoid unsafe casts

Use SomeGeneric<Any> when the container truly stores Any

val settings: Box<Any> = Box("dark")
settings.value = 10
settings.value = false

Pattern:

  • mixed-type storage
  • configuration maps
  • simple dynamic containers

Guard clauses and safe checks

When you receive a star-projected type, you often combine it with type checks:

fun process(box: Box<*>) {
    val value = box.value
    if (value !is String) return

    println(value.uppercase())
}

This is a common guard-clause pattern.

Validation at API boundaries

fun validate(input: List<*>) {
    for (item in input) {
        require(item != null) { "List contains null" }
    }
}

Error handling and unknown generic data

When dealing with external data, the exact type is often unknown at first. Developers use *, then narrow the type safely with checks.

fun handle(data: Any) {
    when (data) {
        is List<*> -> println("List size: ${data.size}")
        else -> println("Unsupported type")
    }
}

Common Mistakes

1. Thinking * means the same as Any

This is the most common mistake.

  • Any = a specific known type
  • * = an unknown type argument

Broken assumption:

class Box<T>(var value: T)

val stringBox: Box<String> = Box("hello")
// val anyBox: Box<Any> = stringBox

This does not compile.

2. Forgetting that generics are invariant by default

val strings: MutableList<String> = mutableListOf("a", "b")
// val items: MutableList<Any> = strings

This is unsafe, so Kotlin rejects it.

How to avoid it:

  • learn invariance, covariance, and contravariance
  • use List<*>, List<out Any>, or a properly variant type when appropriate

3. Trying to write into a star-projected mutable generic

class Box<T>(var value: T)

fun update(box: Box<*>) {
    // box.value = 123
}

This fails because the real type is unknown.

How to avoid it:

  • only read from star-projected values
  • if you need to write, use a more specific type parameter

4. Forgetting that Any does not include null

val box: Box<Any> = Box("hello")
// box.value = null

This does not compile.

Use Any? if null should be allowed.

5. Overusing unsafe casts

fun risky(box: Box<*>) {
    val stringBox = box as Box<String>
    println(stringBox.value.uppercase())
}

This may crash at runtime.

How to avoid it:

  • prefer is checks
  • use safe casts as?
  • narrow types carefully

Comparisons

TypeMeaningCan represent Box<String>?Can safely write arbitrary Any?Typical use
Box<Any>A box whose type argument is exactly AnyNoYesMixed-type container
Box<Any?>A box whose type argument is exactly Any?NoYes, including nullMixed-type container that allows null
Box<*>A box of unknown type argumentYesNoInspect/read unknown generic values

* vs out Any

In many read-only situations, Box<*> behaves similarly to a projected read-only view, but the meaning is different.

  • Box<*> = unknown type argument
  • Box<out Any> = explicitly projected as producing Any

Any vs Any?

TypeAllows null?
AnyNo
Any?Yes

List<*> vs List<Any>

val strings: List<String> = listOf("a", "b")
val unknowns: List<*> = strings
// val anys: List<Any> = strings // only works because List is covariant

This example is a good reminder that variance on the class declaration matters.

List is declared as List<out T>, which is why List<String> can be used as List<Any>. That does not mean all generic types behave that way.

Take Quiz

Cheat Sheet

  • Any = root type for all non-null objects in Kotlin
  • Any? = root type including null
  • SomeGeneric<Any> = generic type with the exact type argument Any
  • SomeGeneric<*> = generic type with an unknown type argument

Key rule

SomeGeneric<*> is not the same as SomeGeneric<Any>.

Use SomeGeneric<Any> when

  • you truly want the container to store Any
  • you need to write different non-null types into it

Use SomeGeneric<*> when

  • you do not know the type argument
  • you only need safe access without assuming a specific type

Safety intuition

  • known type (Any) -> more operations allowed
  • unknown type (*) -> fewer operations allowed

Common examples

class Box<T>(var value: T)

val a: Box<Any> = Box("hello")
a.value = 123

val b: Box<*> = Box("hello")
val x = b.value
// b.value = 123 // not allowed

Important edge cases

  • Any does not allow null
  • star projection is especially useful for APIs and helpers
  • invariance prevents Box<String> from automatically becoming Box<Any>
  • declaration-site variance (out / in) changes assignability rules

FAQ

Is * just a wildcard version of Any in Kotlin?

No. * means the type argument is unknown. Any is a specific concrete type.

Why can't Box<String> be assigned to Box<Any>?

Because Box is invariant by default. Allowing that assignment could let you put a non-String value into a Box<String>.

When should I use Box<*>?

Use it when you need to accept a generic type without caring what its type argument is, especially for reading, logging, or inspection.

Can I write values into Box<*>?

Usually no, because the real type argument is unknown.

Is Any the same as Java's Object?

Roughly yes as the root non-null type, but Kotlin's type system also separates nullable and non-null types.

What is star projection in Kotlin?

Star projection is Kotlin's way of saying “this generic type has some type argument, but we do not know which one.”

Is List<*> the same as List<Any>?

No. They are related but not identical. List<*> means unknown element type. List<Any> means the list produces Any values. With List, covariance makes assignment more flexible.

Take Quiz

Mini Project

Description

Build a small Kotlin program that inspects generic boxes without needing to know their exact type parameter. This demonstrates when Box<*> is useful and why it is safer than assuming Box<Any>.

Goal

Create a reusable function that accepts boxes of different types, prints their values, and safely handles type-specific logic.

Requirements

  • Create a generic Box<T> class.
  • Create at least three boxes with different type arguments such as String, Int, and Boolean.
  • Write a function that accepts Box<*> and prints the value.
  • Add a type check so strings are handled differently from other values.
  • Show that Box<String> can be passed to Box<*> logic, but not to Box<Any>.
Take Quiz

Keep learning

Related questions

View more