Introduction
Moshi enforces strict JSON parsing: when a Kotlin data class has a non-nullable property without a default value, Moshi requires that property to be present in the JSON. If the API omits the field, Moshi throws a JsonDataException with a clear message about which property is missing. While this is safer than silently inserting null, it causes crashes when APIs evolve or return incomplete data.
Symptoms
com.squareup.moshi.JsonDataException: Required property 'email' missing at $- Deserialization fails entirely when one field is absent
- Works for some API responses but not others
- Error message identifies the exact missing property
- Crash occurs after API adds optional fields
Example error:
``
com.squareup.moshi.JsonDataException: Required property 'email' missing
at $.path[0] in response.json
at com.squareup.moshi.internal.Util.missingProperty(Util.java:623)
at com.example.UserJsonAdapter.fromJson(UserJsonAdapter.kt:32)
Common Causes
- API does not return a field that the Kotlin data class requires
- New API version restructures the response
- Field renamed in API but Kotlin model not updated
- Conditional fields: API only returns certain fields in certain states
- Using Kotlin data class with non-nullable properties for API data
Step-by-Step Fix
- 1.Make the property nullable with a default:
- 2.```kotlin
- 3.// Before: crashes if email is missing
- 4.data class User(
- 5.val id: String,
- 6.val name: String,
- 7.val email: String // Required - crash if missing
- 8.)
// After: gracefully handles missing email data class User( val id: String, val name: String, val email: String? = null // Optional with default ) ```
- 1.Use Moshi Kotlin reflection with default values:
- 2.```kotlin
- 3.// Moshi's Kotlin integration respects default values
- 4.data class User(
- 5.val id: String,
- 6.val name: String,
- 7.val email: String = "unknown@example.com",
- 8.val role: String = "user"
- 9.)
val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) // Required for default value support .build()
val adapter = moshi.adapter(User::class.java)
// JSON missing email and role - uses defaults val user = adapter.fromJson("""{"id":"1","name":"John"}""") // user = User(id=1, name=John, email=unknown@example.com, role=user) ```
- 1.Handle nested object that might be missing:
- 2.```kotlin
- 3.data class ApiResponse(
- 4.val data: Data? = null,
- 5.val error: ErrorInfo? = null
- 6.)
data class Data( val user: User? = null, val settings: Settings = Settings() // Default empty settings )
data class Settings( val notifications: Boolean = true, val theme: String = "system" ) ```
- 1.Create a custom adapter for complex defaults:
- 2.```kotlin
- 3.class UserAdapter {
- 4.@FromJson
- 5.fun fromJson(reader: JsonReader): User {
- 6.var id: String? = null
- 7.var name: String? = null
- 8.var email: String? = null
reader.beginObject() while (reader.hasNext()) { when (reader.nextName()) { "id" -> id = reader.nextString() "name" -> name = reader.nextString() "email" -> email = reader.nextString() else -> reader.skipValue() } } reader.endObject()
return User( id = id ?: throw JsonDataException("id is required"), name = name ?: "Anonymous", email = email ) } } ```
Prevention
- Always add
KotlinJsonAdapterFactoryto Moshi for default value support - Use nullable types with defaults for all API response properties
- Use
valwith default values instead oflateinitfor JSON models - Add integration tests that parse actual API responses
- Use Moshi's
@Json(name = "alternative_name")for field renaming - Monitor JsonDataException rates to catch API changes early