KSON — auto-generate GSON adapters for Kotlin data classes
Kotlin data classes are an ideal fit for API response models. They are concise, immutable by default, and come with auto-generated equals, hashCode, copy, and toString — everything you need from a value object representing a network payload. Pairing them with GSON is natural and widely practiced across the Android ecosystem.
However, there is a subtle but serious trap hiding in that combination — one that silently violates Kotlin’s type safety guarantees and can cause crashes that are difficult to reproduce and frustrating to diagnose.
The Problem: GSON Breaks Kotlin’s Null Safety Contract
Kotlin’s null safety is one of its most valuable features. When you declare a property as non-nullable (val id: Int), the compiler enforces at every call site that this value cannot be null. The type system becomes a correctness guarantee you can rely on throughout your codebase.
GSON’s default deserialization strategy, however, does not understand Kotlin’s nullability. It uses ReflectiveTypeAdapterFactory, which bypasses normal object construction entirely. Instead of calling your Kotlin constructor — which enforces null checks — GSON uses sun.misc.Unsafe.allocateInstance() to create objects directly from bytecode, then sets fields via reflection.
The practical consequence: GSON will happily deserialize null into a non-nullable Kotlin field, and your code will not know until something dereferences it:
data class Entity(val id: Int)
val json = """{ "id": null }"""
val entity = gson.fromJson(json, Entity::class.java)
entity.id // <- Throws NPE
What makes this especially dangerous is where the exception surfaces. The failure does not happen at the parsing boundary — where it would be easy to catch, log, and handle — but somewhere deep in business logic, minutes or hours later, in code that has every reason to trust that entity.id is safe to use. This produces NullPointerExceptions in code that looks correct, coming from a source that the developer never suspects.
This is not a theoretical edge case. Malformed API responses, backend bugs, schema evolution, and network corruption all produce null fields from time to time. When they do, a reflection-based deserializer will pass the corruption silently downstream.
Why There Was No Easy Fix
The root cause lies in a fundamental incompatibility between the JVM type system and Kotlin’s nullability model.
Kotlin’s non-nullability is a compile-time construct. At the bytecode level — the level GSON operates at — a val id: Int is indistinguishable from a var id: Int?. Nullability annotations are present in the Kotlin metadata attached to the class, but GSON’s reflection-based adapter does not read them. It sees a plain Java int field and writes whatever value it finds in the JSON, including null (which becomes 0 for primitives, or an actual null reference for objects).
The correct fix requires generating a custom type adapter for each data class — one that calls the actual Kotlin constructor, which performs null checks at instantiation time. Writing these by hand is tedious and error-prone. Every time a class changes, the adapter must be updated. Forgetting to update it reintroduces the bug silently.
The Java Precedent
This problem has a well-established precedent in Java. The AutoValue library generates immutable value classes from abstract definitions, and the companion AutoGson extension generates matching GSON type adapters automatically. Java teams used this combination to get safe, efficient GSON deserialization without boilerplate.
When Kotlin was introduced and teams began migrating their model classes to data classes, the AutoValue+AutoGson combination no longer applied — data classes have their own, distinct mechanism. At the time I built KSON, there was no equivalent library for Kotlin. KSON was written to fill that gap.
How KSON Works
KSON uses Kotlin’s annotation processing (kapt) to generate type adapters at compile time. Annotate a data class with @Kson:
@Kson
data class Entity(val id: Int)
During compilation, the annotation processor reads the class’s constructor parameters, their types, and their names, then generates a concrete TypeAdapter<Entity> that calls the Kotlin constructor directly. If a required field is null in the JSON, the adapter throws a JsonParseException at the parsing boundary — exactly where the error belongs.
There is no runtime reflection, no dynamic proxy, and no startup cost. The generated adapter is a plain Kotlin class that GSON uses like any other hand-written adapter. The correctness guarantee is identical to writing the adapter by hand, and it stays in sync with your model class automatically.
For larger projects with many model classes, @KsonFactory generates a TypeAdapterFactory that registers all generated adapters in one call:
@KsonFactory
object FactoryProvider {
get() = KsonFactoryProvider() // generated class
}
val gson = GsonBuilder()
.registerTypeAdapterFactory(FactoryProvider.get())
.create()
This keeps the GSON configuration clean regardless of how many model classes the project has.
Ergonomics and Adoption
The design intentionally keeps the API surface minimal. Adding KSON to an existing class requires one annotation. Removing it requires deleting one line. There is no configuration file, no code generator to run manually, and no interface to implement. If you decide KSON is not right for your project, the removal is surgical.
This matters in the real world. Libraries that require significant adoption effort see slow uptake and are often abandoned midway through migration. A library that can be incrementally adopted — one class at a time, with instant rollback — is far more likely to be used consistently.
Beyond convenience, the performance argument is meaningful at scale. Reflection-based deserialization is measurably slower than custom type adapters. On mobile devices where CPU resources are limited and startup time is user-visible, the difference adds up across the hundreds of API calls a typical app makes per session.
Impact
GSON remains one of the most widely used JSON libraries in Android development. The null safety vulnerability it creates when combined with Kotlin data classes affects any project that has not explicitly written or generated custom type adapters — which is the majority. KSON provides the missing piece: a zero-configuration, compile-time solution that restores the null safety guarantees Kotlin developers expect and makes reflection-based deserialization of Kotlin classes safe by default.
More info on GitHub: https://github.com/aafanasev/kson
This article was originally posted on Medium.