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?
answer
- KClass = raw class only, drops arguments
- typeOf<T>() returns a full KType
- KType.arguments = nested KTypeProjection list
- typeOf needs concrete or reified type
- Super type token: anonymous TypeReference/TypeToken subclass
basics
~10 sA 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.
solid answer
~40 sA `KClass`/`Class` token is the *raw* class only, so the nested arguments of `Map<String, List<User>>` are lost. To capture the full structure you need a representation of the whole type, not just its head class. In pure Kotlin that's `typeOf<Map<String, List<User>>>()`, which returns a `kotlin.reflect.KType` carrying `arguments` (each a `KTypeProjection` with nullability and variance). `typeOf` is an inline intrinsic, so it must be fed a concrete type or a `reified` parameter — you cannot call `typeOf<T>()` unless T is reified. On the Java/Jackson side the equivalent trick is the **super type token**: an anonymous subclass like `object : TypeReference<Map<String, List<User>>>() {}` whose generic superclass is readable via `getGenericSuperclass()`, because that supertype information is *not* erased. Gson's `TypeToken` works the same way. You pass the resulting KType/TypeReference to a deserializer to reconstruct the nested generics.
code
kotlin · 10 linesimport kotlin.reflect.typeOf
import com.fasterxml.jackson.core.type.TypeReference
// Pure Kotlin: full type via KType
val kt = typeOf<Map<String, List<User>>>()
val valueElem = kt.arguments[1].type!! // List<User>
// Jackson interop: super type token keeps generics
val ref = object : TypeReference<Map<String, List<User>>>() {}
val parsed: Map<String, List<User>> = mapper.readValue(json, ref)go deeper
Recognizes that a class token loses nested type arguments and that something more is needed.
Uses typeOf<T>() and a TypeReference/TypeToken correctly and reads KType.arguments.
Explains why super type tokens defeat erasure (supertypes aren't erased) and the reified constraint on typeOf.
Designs deserialization APIs choosing between KType, TypeReference, and reified wrappers for interop and caching.
## Why a class token isn't enough Erasure removes type *arguments*, and a `KClass`/`Class` token only ever holds the **raw** class. `Map::class` is `KClass<Map>` — it has no slot for the key and value types. So for `Map<String, List<User>>` a class token can never round-trip the `String`, `List`, and `User` parts. ## Kotlin answer: typeOf() -> KType `kotlin.reflect.typeOf<T>()` returns a **`KType`**, the runtime model of a *complete* type: - `KType.classifier` — the head class (`KClass`). - `KType.arguments` — a `List<KTypeProjection>`, one per type argument, each holding its own `KType` plus variance (`in`/`out`/invariant). - `KType.isMarkedNullable` — whether it was `T?`. ```kotlin import kotlin.reflect.typeOf val t = typeOf<Map<String, List<User>>>() t.classifier // Map t.arguments[1].type // List<User> t.arguments[1].type!!.arguments[0].type // User ``` **Key limit:** `typeOf` is an `inline` intrinsic resolved at compile time. You may write `typeOf<Map<String, List<User>>>()` with a concrete type, or `inline fun <reified T> capture() = typeOf<T>()`. You may **not** call `typeOf<T>()` for a non-reified `T` — the compiler has nothing concrete to embed. ## Java/library answer: super type tokens The pre-Kotlin idiom is Neal Gafter's **super type token**. Generic *supertype* declarations survive erasure, so you create an anonymous subclass and read its generic superclass: ```kotlin val ref = object : com.fasterxml.jackson.core.type.TypeReference<Map<String, List<User>>>() {} val value: Map<String, List<User>> = mapper.readValue(json, ref) ``` Gson's `TypeToken<T>` and Spring's `ParameterizedTypeReference<T>` use the same anonymous-subclass mechanism. Kotlin's reified `inline fun <reified T> jacksonTypeRef()` helpers build one for you. ## When to use which - Pure Kotlin reflection / kotlinx.serialization internals: `typeOf` / `KType`. - Jackson / Gson / Spring interop: the `TypeReference` / `TypeToken` / `ParameterizedTypeReference` super-type-token form.
- Why can the anonymous-subclass TypeReference recover generics when a normal variable can't?Generic supertype declarations (extends/implements clauses) are stored in class metadata and not erased, so getGenericSuperclass() can read Map<String, List<User>> off the anonymous subclass.
- Can you call typeOf<T>() where T is an ordinary (non-reified) generic parameter?No. typeOf is an inline intrinsic that needs a concrete type at the call site, so T must be reified (which requires the enclosing function to be inline).
A KClass token is a parcel label that only says 'box'; a KType / TypeReference is the full packing list naming every item nested inside.
saying these in an interview costs you the question
- Claiming Map::class preserves key/value types
- Saying typeOf works with any non-reified T
- Confusing KType (full type) with KClass (raw class)
- Not knowing super type tokens rely on a real anonymous subclass, not just the generic call
- Thinking arguments come back as KClass rather than KTypeProjection/KType