DEV Community

Cover image for Gson silent bug that Never Said a Word(Interview Prep)
Aalaa Fahiem
Aalaa Fahiem

Posted on

Gson silent bug that Never Said a Word(Interview Prep)

Here's a bug that looks impossible until you understand Gson.

You have this data class in your Pokedex app:

data class PokemonStat(
    @SerializedName("base_stat") val baseStat: Int,
    val stat: StatInfo
)
Enter fullscreen mode Exit fullscreen mode

A teammate cleans up the code and deletes what looks like a redundant line:

data class PokemonStat(
    val baseStat: Int,        // @SerializedName removed
    val stat: StatInfo
)
Enter fullscreen mode Exit fullscreen mode

It compiles. No red errors. He runs the app — and every Pokemon's baseStat is 0. Not the real 45 or 49. Just 0. Everywhere. And Gson never threw an error, never logged a warning, never said a single word.

If you can explain why this happens, you understand Gson better than most juniors. This is also a favorite interview question. Let's break it fully.


What Gson actually does

When the JSON comes back from the server, Gson goes key by key:

  1. Read a key from the JSON — say base_stat.
  2. Look for a Kotlin property with the exact same name.
  3. Found it? Pour the value in.
  4. Not found? Leave that property at its default value and move on.

That's the entire matching game — exact name match, or nothing.

Now look at the "cleaned up" class. The JSON key is base_stat. The Kotlin property is baseStat. Those are not the same string. Gson looks for a property called base_stat, doesn't find one, shrugs, and leaves baseStat at its default.

The default for an Int is 0. That's your bug.

@SerializedName("base_stat") was never redundant. It was the sticky note telling Gson: "this property is called baseStat in Kotlin, but look for base_stat in the JSON." Delete the note, and Gson stops matching.

Two ways to fix it:

  1. Rename the property to base_stat — works, but breaks Kotlin's camelCase convention.
  2. Put @SerializedName("base_stat") back — keeps the clean name and matches. This is the right one.

But why 0? Why not a crash?

This is the part that surprised me. A missing field feels like it should be an error. It isn't.

Gson treats a missing key as allowed. It builds your object, fills the fields it found, and leaves the rest at their defaults. Think of Gson filling out a paper form: leaving a box blank is fine. It doesn't tear up the form over an empty box.

Here are the defaults you get for a missing key:

Kotlin type Default when the key is missing
Int, Long 0
Double, Float 0.0
Boolean false
String, any object, List null

So a missing Int silently becomes 0. A missing String silently becomes null. No noise either way.

So when does Gson throw?

It's strict about two things — just not about missing keys:

  • Malformed JSON — the text itself is broken (a missing }, a stray comma). Like handing in a form that's been torn in half. Gson can't parse it → it throws.
  • Type clash — the key exists but holds the wrong type, like "base_stat": "hello" (text where an Int belongs). Like writing "banana" in the Age box. Gson tries to convert, fails → it throws.

So the rule to remember: Gson is lenient about missing data and strict about broken or wrong-typed data. A blank box is fine. A torn form or a banana-in-a-number-box is not.


The scary part: Gson vs Kotlin null-safety

Kotlin's biggest promise is null-safety. When you write:

val name: String   // no question mark
Enter fullscreen mode Exit fullscreen mode

Kotlin guarantees this can never be null. The compiler enforces it. You never have to null-check it. That guarantee is the reason Kotlin kills most NullPointerException crashes.

Here's the problem: Gson doesn't respect that guarantee.

Gson doesn't build your object the normal Kotlin way. It sneaks in through reflection and shoves values straight into fields, skipping Kotlin's checks entirely. So if the JSON key is missing, Gson will quietly put null into your non-null String — no error, the "can never be null" label still sitting right there.

Then later, somewhere far away, your code does the obvious thing:

val length = pokemon.name.length   // name was secretly null
Enter fullscreen mode Exit fullscreen mode

💥 NullPointerException — and it crashes nowhere near the JSON parsing that actually caused it. Good luck tracing that at 1 AM.

That's the gotcha: Kotlin promised the field could never be null, and Gson broke the promise behind its back.

This is exactly why fields that come from JSON are safer as nullable:

data class Sprites(
    @SerializedName("front_default") val frontDefault: String?   // honest
)
Enter fullscreen mode Exit fullscreen mode

The ? tells Kotlin the truth — this might actually be null — so the compiler forces you to handle the empty case instead of trusting a promise Gson didn't keep. You defuse the bomb before it's armed.


The interview version

If someone asks you about Gson and Kotlin, here's the whole thing in a few lines:

  • Q: How does Gson match JSON to Kotlin? Exact property-name match, or @SerializedName to map a different JSON key. No match → default value.
  • Q: A field comes back as 0 / null and there's no error. Why? The JSON key didn't match the property name. A missing key isn't an error in Gson — the field just keeps its default (0 for Int, null for String).
  • Q: When does Gson actually throw? Malformed JSON, or a type mismatch — not a missing key.
  • Q: What's the danger with non-null Kotlin types? Gson bypasses Kotlin null-safety via reflection and can put null into a non-null field, causing a delayed crash. Model JSON fields as nullable when the API might omit them.

None of this is about memorizing Gson. It's about understanding that Gson stays silent exactly where you'd expect it to shout — and that silence is what makes these bugs sneaky.

Top comments (0)