skip to content

Erasure Workarounds & Limits

When reification is unavailable you pass a Class or KClass token, or capture full generic information with a TypeToken or typeOf. The limits are worth naming too: no reified parameters on class-level generics, and no generic array creation.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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>?

level: juniorimportance: must knowfreq 60%

answer

  1. Erasure removes T at runtime
  2. Pass Class<T> or KClass<T> as a parameter
  3. ::class gives KClass, ::class.java gives Class
  4. .kotlin / .java convert between them
  5. Reified needs inline; token is the non-inline escape

basics

~20 s

You 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 s

Because 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 lines
kotlin
fun <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.kotlin

go deeper

for a junior

Knows generics are erased and that you pass the type as a Class/KClass parameter.

for a middle

Fluently converts KClass<->Class with .java/.kotlin and uses isInstance/cast on the token.

for a senior

Explains the non-inline vs inline-reified split and why libraries expose Class<T> cores with reified wrappers.

for a principal

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)

context

open as a page

Why can't you put `reified` on the type parameter of a regular class, a constructor, or a non-inline function, and what is the standard workaround when you genuinely need the type inside one of those?

level: middleimportance: must knowfreq 50%

basics

~20 s

Reified only works on inline functions because the compiler copies the function body and bakes the real type in. Classes and normal functions exist once at runtime, so there's nothing to bake into. The fix: pass a Class<T>/KClass<T> token instead.

open as a page

A KClass<T> only captures the raw class, so passing Map::class loses the key/value types. How do you capture a FULL generic type such as Map<String, List<User>> at runtime in Kotlin, and what does typeOf() return?

level: middleimportance: should knowfreq 45%

basics

~10 s

A class token forgets the inside types. To keep the whole shape like Map<String, List<User>>, use typeOf<...>() (or a library TypeReference). It gives you a full KType describing every nested type argument.

open as a page

Inside a generic function `fun <T> makeBuffer(size: Int)`, why can't you just write `Array<T>(size) { ... }` to create a generic array, and what are the idiomatic Kotlin ways around this limitation?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Arrays need to know their exact element type at runtime, but T is erased, so the runtime has no real type to build. Workarounds: use a reified inline function, pass a Class<T> token, build an Array<Any?> and cast, or just use a List/ArrayList.

open as a page

You're designing a generic deserialization API that must support nested generics like List<Map<String, User>> and stay performant. Compare the runtime type-information options (KClass token, KType via typeOf, super type token / TypeReference, reified wrapper) and how you'd combine them without forcing reflection costs on hot paths.

level: principalimportance: nice to knowfreq 20%

basics

~20 s

A plain class token can't hold nested generics, so you need a full type model: KType (from typeOf) or a TypeReference. Offer a friendly reified entry point that builds it once, then cache the heavy reflection result so repeated calls are cheap.

open as a page