skip to content

You add a new property to a kotlinx.serialization @Serializable class, but old JSON payloads don't contain it. How do you make deserialization of those old payloads still succeed?

level: juniorimportance: must knowfreq 70%

answer

  1. Missing key + no default = MissingFieldException
  2. Add property WITH a default value
  3. Nullable != default; need '= null'
  4. Defaults give backward compatibility
  5. Defaults are evaluated at decode time

basics

~10 s

Give the new property a default value. When the field is missing from the JSON, kotlinx.serialization uses the default, so old messages still load without errors.

solid answer

~40 s

Add the new property with a default value, e.g. `val role: String = "USER"`. kotlinx.serialization treats a missing JSON key as 'use the default', so payloads created before the field existed still deserialize. Without a default, a missing required key throws `MissingFieldException` (a `SerializationException`). Defaults are the primary backward-compatibility tool: new readers can consume old data. Note the default expression must be valid Kotlin and is evaluated at decode time. If you also want to tolerate the inverse — old readers seeing keys they don't know — that's a separate concern solved by `ignoreUnknownKeys`. For optional-and-absent semantics you can combine a default with a nullable type (`val role: String? = null`) to distinguish 'absent' handling from a real value.

code

kotlin · 9 lines
kotlin
@Serializable
data class Config(
    val host: String,
    val port: Int = 8080,        // added later; old JSON omits it
    val tls: Boolean = false
)

val old = """{"host":"db"}"""
val c = Json.decodeFromString<Config>(old) // port=8080, tls=false

go deeper

for a junior

Knows to add a default value so old payloads still deserialize.

for a middle

Distinguishes nullable vs default and names MissingFieldException.

for a senior

Connects the choice to backward vs forward compatibility and encoding omission of defaults.

for a principal

Frames defaults as part of a versioning policy and weighs payload-size vs explicitness tradeoffs across services.

## The problem: schema evolution Schema evolution means changing a serialized data shape over time while old and new versions still interoperate. **Backward compatibility** = a new reader can read old data. **Forward compatibility** = an old reader can read new data. ## Adding a field (backward compatibility) When you add a property to a `@Serializable` class, JSON written by the previous version won't contain that key. kotlinx.serialization's rule is simple: - **No default, key missing** → throws `MissingFieldException` (subtype of `SerializationException`). - **Has a default, key missing** → the default is used. So the fix is to always give newly added properties a **default value**. ```kotlin import kotlinx.serialization.* import kotlinx.serialization.json.Json @Serializable data class User( val id: Long, val name: String, val role: String = "USER" // added in v2; old JSON lacks it ) val oldJson = """{"id":1,"name":"Ada"}""" val user = Json.decodeFromString<User>(oldJson) // user.role == "USER" (default applied, no exception) ``` ## Why not just make it nullable? A nullable type `String?` is itself a value, not a default. `val role: String?` with **no** `= null` is still a *required* key — a missing key throws. You must write `val role: String? = null` to make absence acceptable. Nullable answers 'can the value be null?'; default answers 'what if the key is absent?'. They are orthogonal. ## Encoding side By default, kotlinx.serialization **omits** a property whose value equals its default when encoding (see `encodeDefaults`/`@EncodeDefault`). That keeps payloads small but means consumers must rely on defaults too. ## Recall Missing-required-key is the most common runtime serialization failure in evolving systems; defaults are the cheapest cure.

  • What exception type is thrown when a required key is missing?
    MissingFieldException, which is a subclass of SerializationException.
  • Does making the property nullable alone fix the missing-key error?
    No. You must also supply a default (`= null`); a nullable type without a default is still a required key.

A default value is like a form field pre-filled with a sensible answer: if the sender leaves it blank, the form is still valid.

saying these in an interview costs you the question

  • Claiming a nullable type alone makes the key optional
  • Saying missing keys silently become null without a default
  • Believing you must write a custom serializer for a simple added field
  • Thinking defaults are evaluated once at class load, not at decode

context