JSON Parsing in Kotlin — Which Library Should You Use?
A side-by-side comparison of org.json, Gson, Moshi, Kotlin Serialization, and Jackson for JSON parsing in Kotlin.
This is Part 6 — the final post in the JSON Parsing in Kotlin series. We’ve parsed the same JSON response with five different libraries. Now let’s put them all side by side and figure out which one you should actually use.
The JSON (One Last Time)
Throughout this series, we parsed this JSON — a typical SaaS API response with users, subscriptions, teams, and pagination:
{
"status": "success",
"data": {
"users": [
{
"id": "usr_a1b2c3d4",
"name": "Navkrishna",
"email": "[email protected]",
"role": "ADMIN",
"verified": true,
"createdAt": "2026-01-15T10:30:00Z",
"profile": {
"avatar": "https://cdn.example.com/avatars/nav.jpg",
"bio": "Full-stack developer",
"social": {
"github": "krrishnaaaa",
"twitter": null,
"linkedin": "navkrishna"
}
},
"subscription": {
"plan": "PRO",
"billingCycle": "YEARLY",
"price": 199.99,
"features": ["analytics", "api_access", "priority_support"],
"trialEndsAt": null
},
"teams": [
{
"id": "team_x1y2",
"name": "Backend",
"role": "OWNER",
"memberCount": 5
},
{
"id": "team_z3w4",
"name": "Mobile",
"role": "MEMBER",
"memberCount": 3
}
],
"lastLoginAt": "2026-03-14T18:45:00Z"
},
{
"id": "usr_e5f6g7h8",
"name": "Priya Sharma",
"email": "[email protected]",
"role": "MEMBER",
"verified": false,
"createdAt": "2026-03-01T14:00:00Z",
"profile": {
"avatar": null,
"bio": null,
"social": {
"github": "priya-dev",
"twitter": "priyacodes",
"linkedin": null
}
},
"subscription": {
"plan": "TRIAL",
"billingCycle": null,
"price": 0.0,
"features": ["analytics"],
"trialEndsAt": "2026-03-31T23:59:59Z"
},
"teams": [],
"lastLoginAt": null
}
],
"pagination": {
"page": 1,
"perPage": 20,
"totalItems": 2,
"totalPages": 1,
"hasNext": false
}
},
"meta": {
"requestId": "req_9k8j7h6g",
"timestamp": "2026-03-15T00:00:00Z",
"apiVersion": "v2"
}
}
The Comparison Table
| Feature | org.json | Gson | Moshi | Kotlin Serialization | Jackson |
|---|---|---|---|---|---|
| Lines of parsing code | ~100 | ~5 | ~5 | ~3 | ~5 |
| Null safety | Manual (isNull()) | Silent nulls in non-null fields | Fails on null in non-null | Enforced by compiler | Kotlin module aware |
| Kotlin-native | No | No | Partial (codegen) | Yes | No (module bridge) |
| Reflection | N/A (manual) | Yes | Optional (codegen) | No (compiler plugin) | Yes |
| Code generation | None | None | Annotation processor | Compiler plugin | None |
| Date handling | Manual | Custom adapter | Custom adapter | Custom serializer | JavaTimeModule |
| Android support | Built-in | Good | Excellent | Excellent | Heavy |
| Spring Boot support | None | Manual setup | Manual setup | Growing | Default (built-in) |
| Bundle size | ~80 KB | ~280 KB | ~150 KB | ~500 KB | ~1.5 MB |
| Kotlin Multiplatform | No | No | No | Yes | No |
| Proguard rules needed | No | Yes | Yes (reflection) / No (codegen) | No | Yes |
Code Comparison
Here’s the parse call for the same JSON with all five libraries:
org.json — Manual Parsing
import org.json.JSONObject
fun parse(jsonString: String): ApiResponse {
val root = JSONObject(jsonString)
val status = root.getString("status")
val data = root.getJSONObject("data")
val users = parseUsers(data.getJSONArray("users"))
val pagination = parsePagination(data.getJSONObject("pagination"))
val meta = parseMeta(root.getJSONObject("meta"))
return ApiResponse(status, Data(users, pagination), meta)
}
// + ~90 more lines of parseUser(), parseProfile(), parseTeams()...
Gson
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.time.Instant
val gson: Gson = GsonBuilder()
.registerTypeAdapter(Instant::class.java, InstantDeserializer())
.create()
fun parse(jsonString: String): ApiResponse {
return gson.fromJson(jsonString, ApiResponse::class.java)
}
Moshi (with codegen)
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
val moshi: Moshi = Moshi.Builder()
.add(InstantAdapter())
.addLast(KotlinJsonAdapterFactory())
.build()
fun parse(jsonString: String): ApiResponse {
val adapter = moshi.adapter(ApiResponse::class.java)
return adapter.fromJson(jsonString)!!
}
Kotlin Serialization
import kotlinx.serialization.json.Json
val json = Json { ignoreUnknownKeys = true }
fun parse(jsonString: String): ApiResponse {
return json.decodeFromString<ApiResponse>(jsonString)
}
Jackson
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
val mapper: ObjectMapper = ObjectMapper()
.registerKotlinModule()
.registerModule(JavaTimeModule())
fun parse(jsonString: String): ApiResponse {
return mapper.readValue<ApiResponse>(jsonString)
}
The difference is stark. org.json needs ~100 lines. Every other library needs ~5 or fewer. The real differences between Gson, Moshi, Kotlin Serialization, and Jackson are in the setup and safety guarantees, not the parse call itself.
When to Use What
Android app — Kotlin Serialization or Moshi
Both are lightweight, fast, and designed for mobile. Kotlin Serialization is the newer, Kotlin-native choice with no reflection. Moshi with codegen is the battle-tested alternative. Either way, you get small APK size and no Proguard headaches.
If you’re starting a new Android project today, go with Kotlin Serialization. Google recommends it, and it’s the direction the ecosystem is moving.
Spring Boot — Jackson (default) or Kotlin Serialization
Jackson is already included in Spring Boot. Zero configuration, zero additional dependencies. If you’re in the Spring ecosystem, Jackson is the path of least resistance.
Kotlin Serialization support in Spring is improving and can be configured as an alternative, but Jackson remains the pragmatic choice for most Spring Boot projects.
Kotlin Multiplatform — Kotlin Serialization (only option)
If you’re sharing code across JVM, JS, Native, or Wasm, Kotlin Serialization is your only choice. The other libraries are JVM-only. This alone makes it the future-proof pick.
Legacy codebase — Gson
If you’re maintaining an existing codebase that already uses Gson, there’s no urgent reason to migrate. Gson works fine with Kotlin data classes (with some caveats around null safety). Just be aware that it’s in maintenance mode — don’t expect new features.
Quick script or prototype — org.json
If you’re writing a quick script to extract one field from a JSON file, org.json is fine. No model classes, no setup — just JSONObject(string).getString("key"). But the moment your JSON gets complex, switch to a real library.
The Verdict
For most Kotlin projects in 2026, the answer is:
- Kotlin Serialization — if you want the Kotlin-native, compile-time safe, multiplatform-ready approach
- Jackson — if you’re in Spring Boot and want zero-config JSON that just works
- Moshi — if you want a mature, Android-optimized library with a great Kotlin story
Gson and org.json have their place, but they’re not where you want to start a new project.
Series Recap
| Part | Library | Post |
|---|---|---|
| 1 | org.json | Manual Parsing with org.json |
| 2 | Gson | Parsing with Gson |
| 3 | Moshi | Parsing with Moshi |
| 4 | Kotlin Serialization | Parsing with Kotlin Serialization |
| 5 | Jackson | Parsing with Jackson |
| 6 | Comparison | This post |
Looking for Java? The same comparison without Kotlin Serialization is here.