skip to content

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%

answer

  1. KClass = raw class only, drops arguments
  2. typeOf<T>() returns a full KType
  3. KType.arguments = nested KTypeProjection list
  4. typeOf needs concrete or reified type
  5. Super type token: anonymous TypeReference/TypeToken subclass

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.

solid answer

~40 s

A `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 lines
kotlin
import 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

for a junior

Recognizes that a class token loses nested type arguments and that something more is needed.

for a middle

Uses typeOf<T>() and a TypeReference/TypeToken correctly and reads KType.arguments.

for a senior

Explains why super type tokens defeat erasure (supertypes aren't erased) and the reified constraint on typeOf.

for a principal

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

context