skip to content

What are the ways to obtain a KSerializer instance in kotlinx.serialization, and how do Type.serializer() and the top-level serializer<T>() differ?

level: middleimportance: must knowfreq 55%

answer

  1. Type.serializer() = plugin-generated, fastest, compile-time
  2. Generic class: serializer takes child serializers (Box.serializer(User.serializer()))
  3. serializer<T>() reified uses typeOf<T>() for nested generics
  4. serializer(kType) for runtime KType
  5. ListSerializer / MapSerializer / .nullable combinators

basics

~10 s

You can call the generated MyType.serializer() function on a @Serializable class, or use the top-level serializer<T>() / serializer(KType) functions to look one up, including for generic types like List<User>.

solid answer

~40 s

Three common paths: (1) `MyType.serializer()` — the function the compiler plugin generates for each @Serializable class; it is compile-time resolved and returns the exact KSerializer<MyType>. For generic classes the plugin generates an overload taking the type arguments' serializers, e.g. `Box.serializer(User.serializer())`. (2) The reified top-level `serializer<T>()` (in kotlinx.serialization), which uses `typeOf<T>()` to capture the full `KType`, then resolves a serializer — handling nested generics like `List<Map<String, User>>`. (3) `serializer(kType: KType)` for when you only have a runtime `KType`. There is also `PrimitiveSerializer`-style built-ins (`Int.serializer()`, `String.serializer()`) and `ListSerializer(elementSerializer)`. `Type.serializer()` is the most efficient (no reflection, no KType machinery); the top-level functions trade a little overhead for handling arbitrary generic types.

code

kotlin · 14 lines
kotlin
@Serializable data class User(val id: Int)
@Serializable data class Box<T>(val value: T)

// 1. generated
val a: KSerializer<User> = User.serializer()
// 1b. generic generated overload
val b: KSerializer<Box<User>> = Box.serializer(User.serializer())
// 2. reified top-level, handles nesting
val c: KSerializer<List<User>> = serializer()
// 3. from a runtime KType
val kType = typeOf<Map<String, User>>()
val d: KSerializer<Any?> = serializer(kType)
// 4. combinator
val e: KSerializer<List<User>> = ListSerializer(User.serializer())

go deeper

for a junior

Knows MyType.serializer() exists for @Serializable classes and that you pass it to Json.

for a middle

Distinguishes generated vs reified vs KType-based lookup and knows generic classes need child serializers.

for a senior

Explains how reified serializer<T>() uses typeOf<T>() to defeat erasure for nested generics and when frameworks use serializer(kType).

for a principal

Reasons about performance (no-reflection generated path vs KType resolution) and library-design implications for framework integration points.

## The goal Before you can encode/decode, you need a `KSerializer<T>`. kotlinx.serialization offers several ways to get one, ranging from zero-overhead compile-time lookups to fully runtime-type-driven resolution. ## 1. Generated `Type.serializer()` For every `@Serializable` class the compiler **plugin generates** a function: ```kotlin @Serializable data class User(val id: Int, val name: String) val s: KSerializer<User> = User.serializer() // generated, compile-time ``` This is the cheapest path — no reflection, no `KType`. For **generic** serializable classes the plugin generates an overload that takes serializers for each type parameter: ```kotlin @Serializable data class Box<T>(val value: T) val s: KSerializer<Box<User>> = Box.serializer(User.serializer()) ``` You must supply the inner serializers yourself — the function can't infer them at runtime. ## 2. Top-level reified `serializer<T>()` The library provides: ```kotlin inline fun <reified T> serializer(): KSerializer<T> ``` It uses `reified` + `typeOf<T>()` to capture the **complete** `KType`, including nested generic arguments, then walks it to build a composite serializer: ```kotlin val s: KSerializer<List<Map<String, User>>> = serializer() ``` This is what makes `Json.encodeToString(value)` (the reified, serializer-less overload) work — it calls `serializer<T>()` for you. ## 3. `serializer(kType: KType)` When you only have a `KType` at runtime (e.g. obtained from reflection), call the overload that takes a `KType` and returns `KSerializer<Any?>`. Used by frameworks (e.g. Ktor content negotiation) that don't know `T` statically. ## 4. Built-ins and combinators - Primitives: `Int.serializer()`, `String.serializer()`, `Boolean.serializer()`, etc. - Collections: `ListSerializer(elem)`, `MapSerializer(k, v)`, `SetSerializer(elem)`. - Nullability: `elem.nullable` wraps a serializer to accept null. ## Choosing | Situation | Use | |---|---| | You know the concrete @Serializable type statically | `Type.serializer()` (fastest) | | Arbitrary nested generic, type known at compile time | reified `serializer<T>()` | | Only a runtime KType available | `serializer(kType)` | | Standalone primitive/collection | `Int.serializer()`, `ListSerializer(...)` | ## Gotcha `serializer<T>()` for a type that isn't @Serializable (and has no contextual/built-in serializer) throws `SerializationException` at runtime — the reified version can't be checked at compile time the way `Type.serializer()` can.

  • Why does Box.serializer() require you to pass User.serializer() for Box<User>?
    Type erasure: the generated function for a generic class can't know the type argument at runtime, so you provide the element serializer explicitly. The reified serializer<T>() avoids this by capturing the KType.
  • What happens if you call serializer<NotSerializable>()?
    It throws SerializationException at runtime because no serializer can be resolved; the reified call can't catch this at compile time.

saying these in an interview costs you the question

  • Thinking Type.serializer() uses runtime reflection (it's generated, compile-time)
  • Believing serializer<T>() loses nested generic type arguments (typeOf<T>() preserves them)
  • Not knowing generic classes need child serializers passed in
  • Confusing serializer(kType) (returns KSerializer<Any?>) with reified serializer<T>()

context