The JVM erases generics at runtime, so a method that takes a List<T> cannot 'see' what T is. How do you pass the concrete type into a non-inline function so it survives to runtime, and what is the difference between Class<T> and KClass<T>?
answer
- Erasure removes T at runtime
- Pass Class<T> or KClass<T> as a parameter
- ::class gives KClass, ::class.java gives Class
- .kotlin / .java convert between them
- Reified needs inline; token is the non-inline escape
basics
~20 sYou hand the type in by hand as a parameter, like a label. Add a Class<T> or KClass<T> argument so the function knows the real type at runtime. Class is the Java type token; KClass is the Kotlin one.
solid answer
~40 sBecause the JVM erases generic type arguments, a regular (non-inline) function can't recover T at runtime. The classic workaround is the type-token pattern: pass the type explicitly as a parameter. In Java you pass Class<T>; in Kotlin you pass KClass<T>, obtained from the ::class operator (e.g. String::class) or via .javaClass / ::class.java for the Java side. The function then uses that token for reflection: clazz.cast(x), clazz.isInstance(x), or a deserializer like objectMapper.readValue(json, Foo::class.java). Convert between them with KClass.java and Class.kotlin. This is exactly what libraries like Jackson and Spring do for non-reified APIs. A reified type parameter is sugar that captures the token for you, but it only works on inline functions; a plain function needs the explicit Class/KClass argument.
code
kotlin · 9 linesfun <T : Any> decode(bytes: ByteArray, type: KClass<T>): T {
val obj = mapper.readValue(bytes, type.java) // Class<T> for Jackson
return type.cast(obj) // KClass.cast = safe runtime cast
}
val u = decode(raw, User::class)
val k: KClass<User> = User::class
val j: Class<User> = k.java
val back: KClass<User> = j.kotlingo deeper
Knows generics are erased and that you pass the type as a Class/KClass parameter.
Fluently converts KClass<->Class with .java/.kotlin and uses isInstance/cast on the token.
Explains the non-inline vs inline-reified split and why libraries expose Class<T> cores with reified wrappers.
Reasons about API ergonomics: when to take a token, expose a reified overload, or carry a TypeReference for nested generics.
## The problem On the JVM, generic type arguments are **erased** at compile time. At runtime `List<String>` and `List<Int>` are both just `List`. So inside a normal function `fun <T> parse(json: String): T` there is no way to ask 'what was T?'. The information is gone. ## The workaround: type tokens You reintroduce the type by passing it as an ordinary value parameter — a **type token**. Two flavors: - **`Class<T>`** — the Java reflection token. Get it with `Foo::class.java`, or from an instance with `x.javaClass`. - **`KClass<T>`** — the Kotlin reflection token. Get it with `Foo::class`, or from an instance with `x::class`. The `::class` operator is how Kotlin gives you a class reference. `Foo::class` is a `KClass<Foo>`; append `.java` to cross to `Class<Foo>`. Going back: `someClass.kotlin` gives a `KClass`. ```kotlin // Non-inline: T cannot be reified, so take a token. fun <T : Any> fromJson(json: String, type: KClass<T>): T = mapper.readValue(json, type.java) val user: User = fromJson(text, User::class) ``` ## What you do with the token - `type.isInstance(x)` — runtime instanceof check. - `type.cast(x)` (on `Class`) / `type.safeCast(x)` (on `KClass`) — safe runtime cast. - Hand it to a library: `mapper.readValue(json, User::class.java)`. ## Why not just reified? A `reified` type parameter captures the token automatically — but `reified` is only legal on **`inline`** functions. For a normal function, a constructor, or a class type parameter, you cannot use `reified`, so the explicit `Class`/`KClass` token is the canonical escape hatch. Many APIs offer both: a non-inline core taking `Class<T>` plus an `inline reified` convenience wrapper that calls it.
- If you already have an instance, how do you get its runtime class, and will that reflect generic arguments?Use x::class (KClass) or x.javaClass (Class). It gives the erased runtime class only — e.g. ArrayList — not the generic argument, which is still erased.
- Why do Jackson/Spring APIs ask for Class<T> instead of just being generic?Because they are non-inline library methods compiled once; they cannot reify T, so they require the caller to supply the runtime type token.
Erasure rips the label off the box; a type token is you stapling the label back on as a separate sticky note you carry into the function.
saying these in an interview costs you the question
- Claiming a plain generic function can read T at runtime without a token
- Saying Class and KClass are the same object (they are convertible but distinct types)
- Using reified in a non-inline function or a class type parameter
- Thinking x::class recovers the generic argument of a List<String>
- Confusing Foo::class (KClass) with Foo::class.java (Class)