skip to content

What is a SerializersModule in kotlinx.serialization, and when do you need one?

level: juniorimportance: must knowfreq 55%

answer

  1. Runtime registry of KSerializers
  2. SerializersModule { } DSL
  3. Needed for open classes & interfaces, not sealed
  4. Attach via Json { serializersModule = ... }
  5. polymorphic<Base> { subclass(...) }

basics

~10 s

A SerializersModule is a registry that tells the serialization library how to handle types it cannot figure out on its own, like interfaces or open base classes with many possible subtypes.

solid answer

~40 s

A SerializersModule is a runtime registry of serializers, built with the SerializersModule { } DSL. The @Serializable plugin handles concrete classes and sealed hierarchies automatically because the compiler knows all the types. But for open classes and interfaces, the set of subtypes is unbounded, so you must register each subtype explicitly via polymorphic<Base> { subclass(Impl::class) }. You attach the module to a format instance, e.g. Json { serializersModule = myModule }. The module is also where you register contextual serializers. Without it, serializing a value through a polymorphic base type throws SerializationException because no serializer is found at runtime for the concrete subtype.

code

kotlin · 11 lines
kotlin
interface Animal
@Serializable data class Dog(val name: String) : Animal
@Serializable data class Cat(val lives: Int) : Animal

val module = SerializersModule {
    polymorphic(Animal::class) {
        subclass(Dog::class)
        subclass(Cat::class)
    }
}
val json = Json { serializersModule = module }

go deeper

for a junior

Knows it's a registry needed for interfaces/open classes and that you attach it to Json.

for a middle

Explains why sealed differs (compile-time exhaustiveness) and uses the polymorphic { subclass } builder correctly.

for a senior

Discusses combining modules, contextual registration, and the runtime SerializationException when registration is missing.

for a principal

Reasons about module composition across libraries, default-vs-shared module strategy, and how discriminators interact with format choice.

## What it is A `SerializersModule` is a runtime *registry* that maps types to `KSerializer` instances. It exists because some serialization decisions cannot be made at compile time. ## Why the plugin alone isn't enough The `@Serializable` compiler plugin generates a serializer for each annotated class, and for `sealed` hierarchies it can enumerate every subtype because they all live in the same module — that gives *exhaustiveness*. But for an **open class** or an **interface**, anyone can add a subtype anywhere, so the compiler can't build a closed list. You bridge that gap at runtime with a `SerializersModule`. ## Building one ```kotlin val module = SerializersModule { polymorphic(Animal::class) { subclass(Dog::class) subclass(Cat::class) } } ``` The `polymorphic<Base> { subclass(...) }` builder registers each concrete subtype under the base type so the format can pick the right serializer at runtime by reading a type discriminator. ## Attaching it A module does nothing until you wire it into a format: ```kotlin val json = Json { serializersModule = module } ``` `Json`, `Cbor`, `ProtoBuf`, etc. all expose a `serializersModule` property. There is also `EmptySerializersModule()` (the default) and you can combine modules with `plus` / `overwriteWith`. ## What it's used for - **Polymorphic** open/interface hierarchies (`polymorphic { subclass() }`). - **Contextual** serialization (`contextual(MySerializer)`) for types you can't annotate. ## Without it Serializing through a polymorphic base with no registration throws `SerializationException: Class 'Dog' is not registered for polymorphic serialization`.

  • Do you need a SerializersModule for a sealed class hierarchy?
    No. The plugin enumerates all subtypes of a sealed class at compile time and generates the polymorphic serializer automatically, so registration is implicit.
  • What is the default module if you don't set one?
    EmptySerializersModule(), which has no polymorphic or contextual registrations.

It's like a phone book: concrete and sealed types call ahead automatically, but for open hierarchies you must list each subtype's number so the library can reach it at runtime.

saying these in an interview costs you the question

  • Thinking @Serializable alone handles interface/open-class hierarchies
  • Confusing SerializersModule with Json configuration like ignoreUnknownKeys
  • Saying sealed classes require manual registration
  • Building a module but never attaching it to a format
  • Believing the module is compile-time only

context