Ktor ContentNegotiation converter trade-off: kotlinx.serialization strictness versus Jackson unknown-field defaults
0 reputation · 06 Sept 2024, 19:13 UTC
0 reputation · 06 Sept 2024, 19:13 UTC
When configuring ContentNegotiation in a small Ktor application, the choice between kotlinx.serialization and Jackson affects how the service behaves when clients send JSON properties the server model does not declare. The goal is to select a converter that supports a rolling upgrade without rejecting requests containing added fields from updated clients.
kotlinx.serialization generates serializers at compile time, removing reflection overhead and reducing startup latency, but its default configuration may fail on unknown keys unless ignoreUnknownKeys is explicitly enabled. Jackson tolerates unknown properties by default, yet its runtime reflection requires the Kotlin module to handle data classes with default parameters, and its polymorphic APIs remain mature.
The trade-off is localized to the install(ContentNegotiation) block, though data-class annotations and custom serializers may need rewriting if the library changes. Because converter APIs can lag behind serialization library releases, the configuration surface should be checked against the Ktor version in use. The uncertainty is whether strict compile-time validation or Jackson's permissive defaults better fits a zero-downtime migration when payload shapes evolve.
Which converter default should a small Ktor service use during a rolling upgrade: kotlinx.serialization with explicit unknown-key tolerance, or Jackson with the Kotlin module registered? How do the two libraries' unknown-field policies interact with Ktor's currently supported converter APIs? What verification confirms the chosen configuration tolerates extra fields without silently masking schema drift?
35275 reputation · 06 Sept 2024, 23:15 UTC
For a rolling upgrade where newer clients send extra JSON fields, Jackson with the Kotlin module registered is the lower-friction default. Its permissive unknown-field behavior works immediately without configuration changes. kotlinx.serialization requires explicit ignoreUnknownKeys = true (global or per-class) to achieve the same tolerance.
install(ContentNegotiation) { kotlinxSerialization(Json { ignoreUnknownKeys = true }) }@Serializable(ignoreUnknownKeys = true)DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false is the default.install(ContentNegotiation) { jackson { registerModule(KotlinModule()) } }jackson { enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) }Both converter APIs have been stable since Ktor 2.0. The ktor-serialization-kotlinx-json and ktor-serialization-jackson artifacts must match the Ktor core version. No Ktor version blocks either configuration. The trade-off is localized to the install(ContentNegotiation) block, though switching libraries later requires rewriting annotations and custom serializers.
Neither library logs dropped fields by default. To detect schema drift without failing requests:
DeserializationProblemHandler to capture dropped fields.jackson.registeredModuleIds contains kotlin-module; test deserialization of a data class with default parameter values.Does your service use polymorphic deserialization (sealed class hierarchies or subtype handling)? This changes the recommendation because kotlinx.serialization and Jackson handle polymorphism very differently (@Serializable + typeOf() vs @JsonTypeInfo/@JsonSubTypes), and migration cost between them is high for type hierarchies.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.