JSON Errors / Kotlin
JsonDecodingException: Unexpected JSON token at offsetHow to Fix: JsonDecodingException: Unexpected JSON token at offset
kotlinx.serialization stopped decoding because the JSON did not match what your @Serializable class expects. The message tells you where and why: "Unexpected JSON token at offset 0: Expected start of the object '{', but had '[' instead at path: $". The offset is the 0-based character position, and the path ($, $.user.name, $[3]) is where in the structure decoding failed.
Read the reason after the colon. Most cases are a mismatch between the JSON and your Kotlin types, not broken JSON. The exception often ends with a hint naming the Json {} setting that would accept the input, plus a "JSON input:" excerpt around the failure.
Common causes
1. Expected start of the object '{', but had '[' instead
The response is an array but you decoded a single object (or the reverse). Decode into List<User> instead of User, or unwrap the envelope object first.
Json.decodeFromString<User>(body) // body is [{...}, {...}]Json.decodeFromString<List<User>>(body)2. Expected string literal but 'null' literal was found
The API sent null for a property you declared as non-null. Make the property nullable (val nickname: String?), or give it a default value and enable coerceInputValues so null falls back to the default.
3. But had '<' instead: the server returned HTML
An error page, a redirect to a login page, or a wrong base URL in Retrofit or Ktor. Check the HTTP status and log the raw body before decoding.
4. Encountered an unknown key 'x'
The JSON has a field your class does not declare. By default kotlinx.serialization rejects it so you notice API changes. Older versions report this as "Unexpected JSON token at offset N: Encountered an unknown key"; current versions use "Encountered an unknown key 'x' at offset N".
How to fix it
val json = Json {
ignoreUnknownKeys = true // new API fields don't break old clients
coerceInputValues = true // null -> property default for non-null fields
explicitNulls = false // missing keys -> null for nullable fields
}
val users = json.decodeFromString<List<User>>(body)@Serializable
data class User(
val id: Long,
val name: String,
val nickname: String? = null, // API may send null or omit it
@SerialName("created_at") val createdAt: String,
)// Retrofit: com.squareup.retrofit2:converter-kotlinx-serialization (2.11+)
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
// Ktor client
HttpClient {
install(ContentNegotiation) { json(json) }
}Frequently asked questions
Should I just turn on isLenient?
Only for input you know is non-standard, such as unquoted keys or strings from a legacy system. isLenient does not fix type mismatches, nulls, or unknown keys - those have their own settings (coerceInputValues, ignoreUnknownKeys). For comments and trailing commas, newer versions have allowComments and allowTrailingComma.
How do I find the problem in a large response?
Use the path in the message ($.items[12].price) rather than counting characters to the offset. To inspect the raw document, log the body before decoding, or paste it into a JSON viewer and navigate to that path.
What is MissingFieldException?
A related error: a non-null property without a default was absent from the JSON entirely ("Field 'x' is required ... but it was missing"). Give the property a default value or make it nullable with = null.
Last updated:
Related JSON errors
You might also need
Free and in-browser, like everything on JSON Console. Browse all tools →