In kotlinx.serialization, what does @Contextual do and why would you need it for a type like java.util.Date?
answer
- Compile-time resolution fails for types you can't annotate
- @Contextual = defer to runtime lookup
- SerializersModule + contextual(...) = where you register
- Missing registration => runtime SerializationException
- @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 skotlinx.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@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
Knows @Contextual is for types you can't annotate like Date, and that you register a serializer separately.
Explains compile-time vs runtime resolution and that the SerializersModule supplies the serializer via contextual().
Discusses the lost compile-time safety trade-off, @file:UseContextualSerialization, and when to prefer @Serializable(with=...) instead.
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