skip to content

Contextual Serialization

@Contextual defers a property's serializer to one registered in the module, which is how you serialize a type you cannot annotate. It is the answer to 'how do I serialize a java.util.Date with this library'.

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

questions

5

In kotlinx.serialization, what does @Contextual do and why would you need it for a type like java.util.Date?

level: juniorimportance: must knowfreq 55%

answer

  1. Compile-time resolution fails for types you can't annotate
  2. @Contextual = defer to runtime lookup
  3. SerializersModule + contextual(...) = where you register
  4. Missing registration => runtime SerializationException
  5. @file:UseContextualSerialization for bulk

basics

~10 s

@Contextual tells the serializer: don't pick a serializer at compile time for this property. Look one up at runtime from a registered module. You use it for types you can't annotate, like java.util.Date.

solid answer

~40 s

kotlinx.serialization normally resolves a KSerializer at compile time via the @Serializable plugin. For a type you don't own and can't mark @Serializable (e.g. java.util.Date, java.time.Instant, BigDecimal), there is no generated serializer. @Contextual on the property (or @file:UseContextualSerialization) defers resolution: the format consults a SerializersModule at encode/decode time and uses the serializer registered there via contextual(...). If no module provides one, you get a SerializationException at runtime. So @Contextual is the 'I'll supply the serializer separately' marker; the SerializersModule is where you actually supply it. This decouples the data class from the concrete serializer, letting different formats or contexts plug in different strategies for the same type.

code

kotlin · 7 lines
kotlin
@Serializable
data class Event(val name: String, @Contextual val ts: Date)

val json = Json {
    serializersModule = SerializersModule { contextual(DateAsLongSerializer) }
}
json.encodeToString(Event("login", Date()))

go deeper

for a junior

Knows @Contextual is for types you can't annotate like Date, and that you register a serializer separately.

for a middle

Explains compile-time vs runtime resolution and that the SerializersModule supplies the serializer via contextual().

for a senior

Discusses the lost compile-time safety trade-off, @file:UseContextualSerialization, and when to prefer @Serializable(with=...) instead.

for a principal

Frames it as a strategy-injection seam: same type, different serializers per format/context; weighs runtime-failure risk against decoupling for library API design.

## The problem kotlinx.serialization works through a compiler plugin. When you annotate a class with `@Serializable`, the plugin generates a `KSerializer` for it and, for each property, resolves the serializer for that property's type at **compile time**. This works great when every type is either a built-in (Int, String, List) or itself `@Serializable`. But some types you cannot annotate: `java.util.Date`, `java.time.Instant`, `java.math.BigDecimal`, classes from a third-party JAR. You don't own the source, so you can't add `@Serializable`. The plugin then has nothing to resolve and the build fails. ## The solution: @Contextual `@Contextual` is an annotation you place on a property whose type has no compile-time serializer: ```kotlin import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.util.Date @Serializable data class Event( val name: String, @Contextual val timestamp: Date, ) ``` It instructs the generated serializer to emit a **ContextualSerializer** for that slot instead of a concrete one. At runtime, that ContextualSerializer asks the active **SerializersModule** for a serializer registered for `Date::class`. Resolution is therefore deferred from compile time to **encode/decode time**. ## Supplying the serializer: SerializersModule You must write a `KSerializer<Date>` and register it: ```kotlin import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.json.Json val module = SerializersModule { contextual(DateAsLongSerializer) } val json = Json { serializersModule = module } ``` Now `json.encodeToString(Event(...))` works. If you forget to register, you get a `SerializationException: Serializer for class 'Date' is not found` **at runtime**, not at compile time — that loss of compile-time safety is the trade-off. ## File-level alternative If many properties use the same type, annotating each is noisy. Use the file-level annotation: ```kotlin @file:UseContextualSerialization(Date::class) ``` Every `Date` property in the file is then treated as contextual without per-property `@Contextual`. ## Key terms - **KSerializer<T>**: the interface with `serialize`/`deserialize` and a `descriptor`. - **SerializersModule**: a runtime registry mapping classes to serializers. - **contextual(...)**: the DSL function that registers a serializer for a class in a module. - **ContextualSerializer**: the placeholder serializer the plugin emits for `@Contextual` slots; it looks up the real one in the module.

  • What happens if you mark a property @Contextual but never register a serializer for its type?
    Encoding/decoding throws a SerializationException at runtime saying the serializer for that class was not found. There is no compile-time error.
  • Could you avoid @Contextual for Date entirely?
    Yes — write a KSerializer<Date> and pass it directly with @Serializable(with = DateSerializer::class), which resolves at compile time. @Contextual is for when you want runtime/module-based resolution instead.

@Contextual is an IOU: 'I owe you a serializer for this type — collect it from the module at runtime.'

saying these in an interview costs you the question

  • Thinks @Contextual itself provides the serialization logic
  • Believes a missing registration fails at compile time
  • Confuses @Contextual with @Transient (skipping a field)
  • Cannot name SerializersModule or contextual() as the registration mechanism

context

open as a page

Show how to write a KSerializer for java.util.Date and register it so a @Contextual property serializes. What exactly does contextual() bind?

level: middleimportance: must knowfreq 48%

basics

~10 s

Write a class implementing KSerializer<Date> with serialize, deserialize and a descriptor. Then build a SerializersModule with contextual(YourSerializer) and pass it to Json. contextual() binds Date::class to that serializer.

open as a page

Compare @Contextual, @Serializable(with = ...), and @file:UseContextualSerialization. When does each resolve, and which one would you choose?

level: middleimportance: should knowfreq 40%

basics

~10 s

@Serializable(with=...) hard-wires one serializer at compile time. @Contextual defers to a runtime module so it can vary. @file:UseContextualSerialization is just @Contextual applied to every use of a type in a file.

open as a page

Explain exactly how a @Contextual property is resolved at encode time, and what failure modes and edge cases (nullability, generics, missing registration) you should anticipate.

level: seniorimportance: should knowfreq 30%

basics

~10 s

At encode time the ContextualSerializer asks the format's module for a serializer matching the property's class. If none is found it throws. Nullable types and generic types need extra care so the lookup matches.

open as a page

As a principal engineer, when would you mandate @Contextual over @Serializable(with=...) across a large/multiplatform codebase, and what are the systemic risks and governance practices?

level: principalimportance: nice to knowfreq 16%

basics

~20 s

Mandate @Contextual when the same type must serialize differently per platform, format, or consumer, or when a library wants pluggable serializers. Otherwise prefer the compile-time with=. The big risk is runtime failures from a missing or wrong module.

open as a page