Introduction

Moshi strictly enforces that non-nullable Kotlin properties must have a corresponding non-null value in the JSON. When an API omits a required field or returns null for a non-nullable property, Moshi throws JsonDataException: Required property 'x' absent. This commonly happens when APIs evolve, when different endpoints return slightly different response shapes, or when optional fields are documented but not clearly marked.

Symptoms

  • com.squareup.moshi.JsonDataException: Required property 'email' absent
  • Non-null value 'null' for property 'name' when API returns null
  • Works for some API responses but fails for others
  • Deserialization succeeds in development but fails in production
  • Error does not indicate which endpoint or response caused the issue

Error output: `` com.squareup.moshi.JsonDataException: Required property 'phone' absent at $.phone (MyData.kt:8) at com.squareup.moshi.internal.Util.missingProperty(Util.java:616)

Common Causes

  • API does not send a field that the Kotlin model requires
  • API returns null for a field declared as non-nullable
  • New API version removes previously required fields
  • Different endpoints return different response shapes with same model
  • JSON field name mismatch (snake_case vs camelCase)

Step-by-Step Fix

  1. 1.**Make properties nullable for optional API fields":
  2. 2.```kotlin
  3. 3.import com.squareup.moshi.Json
  4. 4.import com.squareup.moshi.JsonClass

// WRONG - all fields required, fails if any are missing @JsonClass(generateAdapter = true) data class User( val id: Long, val name: String, val email: String, val phone: String, // May be missing from API val avatar: String // May be null from API )

// CORRECT - make optional fields nullable @JsonClass(generateAdapter = true) data class User( val id: Long, val name: String, val email: String, val phone: String? = null, // Optional, defaults to null val avatar: String? = null // Nullable, handles null in JSON ) ```

  1. 1.**Use Moshi adapters for default values":
  2. 2.```kotlin
  3. 3.import com.squareup.moshi.FromJson
  4. 4.import com.squareup.moshi.ToJson

// Custom adapter for handling missing fields with defaults class DefaultStringAdapter { @FromJson fun fromJson(string: String?): String { return string ?: "" // Default empty string instead of null }

@ToJson fun toJson(value: String): String? { return value.takeIf { it.isNotEmpty() } } }

val moshi = Moshi.Builder() .add(DefaultStringAdapter()) .add(KotlinJsonAdapterFactory()) .build() ```

  1. 1.**Handle field name mismatches":
  2. 2.```kotlin
  3. 3.@JsonClass(generateAdapter = true)
  4. 4.data class User(
  5. 5.@Json(name = "user_id") val id: Long,
  6. 6.@Json(name = "full_name") val name: String,
  7. 7.@Json(name = "email_address") val email: String,
  8. 8.val phone: String? = null // No @Json needed if names match
  9. 9.)

// API response: // {"user_id": 1, "full_name": "John", "email_address": "john@example.com"} ```

  1. 1.**Use a lenient JSON adapter for tolerant parsing":
  2. 2.```kotlin
  3. 3.import com.squareup.moshi.JsonAdapter
  4. 4.import com.squareup.moshi.JsonReader
  5. 5.import com.squareup.moshi.JsonWriter
  6. 6.import com.squareup.moshi.Moshi
  7. 7.import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory

// Tolerant adapter that skips unknown fields and handles missing required ones object TolerantAdapterFactory { fun create(moshi: Moshi): Moshi { return moshi.newBuilder() .add(TolerantKotlinFactory()) .build() } }

// Use Moshi's built-in leniency val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build()

val adapter = moshi.adapter(User::class.java) .lenient() // Allows top-level values, comments, etc. ```

Prevention

  • Declare optional fields as nullable with default values
  • Use @Json(name = "...") for fields with different JSON names
  • Write deserialization tests against real API responses
  • Monitor JsonDataException in production for API changes
  • Use Moshi codegen (@JsonClass(generateAdapter = true)) for better performance
  • Document which API fields are required vs optional in model classes