Java JSON — Gson's Silent Null Field Loss
Gson's default silently drops null fields in payment responses; Jackson doesn't recognize Gson annotations: use contract tests to catch this.
- Jackson and Gson are Java libraries that convert Java objects to/from JSON strings
- Jackson uses streaming parsing (ObjectMapper) — faster for large payloads
- Gson uses tree model (JsonParser) — simpler for small to medium data
- Performance: Jackson ~30% faster on heavy payloads, Gson uses less memory (~20%)
- In production, mismatched annotations between libraries silently silences null fields
- Biggest mistake: forgetting a no-arg constructor breaks deserialization silently
Imagine you run a restaurant and you need to send your daily menu to five different delivery apps. Instead of calling each app and describing every dish out loud, you write it all down on a standard order form they all understand. JSON is that standard form — a universal way to package data so any app, in any language, can read it. In Java, libraries like Jackson and Gson are the printers and readers of those forms.
Every modern Java application talks to something — a REST API, a mobile client, a microservice, a database cache. And the language they almost all speak is JSON. If your Java code can't fluently read and write JSON, it's essentially mute on the modern web. This isn't a niche skill; it's table stakes for any backend role.
The problem is that Java objects and JSON text are fundamentally different things. A Java object lives in memory with types, references, and methods. JSON is just a flat string of characters. Bridging that gap — turning an object into JSON (serialization) and turning JSON back into an object (deserialization) — is exactly what libraries like Jackson and Gson were built to solve. Without them, you'd be manually parsing curly braces and handling edge cases forever.
By the end of this article you'll know how to pick the right library for your project, serialize and deserialize simple and nested Java objects, handle real-world messiness like missing fields and custom date formats, and avoid the three mistakes that show up in almost every code review involving JSON. You'll walk away ready to wire up a REST client or build an API endpoint without reaching for Stack Overflow.
What is Working with JSON in Java?
JSON (JavaScript Object Notation) is a lightweight data-interchange format. In Java, libraries like Jackson and Gson translate between Java objects and JSON strings. This translation is called serialization (object → JSON) and deserialization (JSON → object). Without these libraries, you'd manually parse string buffers, handle escaping, and manage type conversions — which is error-prone and tedious. The key insight: both libraries rely on reflection, field naming conventions, and type information to map object fields to JSON keys. Misunderstand these basics and you'll waste hours debugging silent output changes.
Jackson vs Gson: When to Use Which
Jackson and Gson are the two dominant JSON libraries in the Java ecosystem. Jackson is the de facto standard for Spring Boot and other enterprise frameworks. It's annotation-driven, deeply customisable, and performs well on large payloads thanks to its streaming parser. Gson, from Google, is simpler to set up (no ObjectMapper configuration needed) and uses less memory — but it lacks Jackson's advanced features like polymorphic deserialization and tree-model modification. Jackson is the default in Spring Boot, but Gson is a solid choice for microservices or Android where simplicity and memory footprint matter more than raw speed. However, here's the real trade-off: Jackson's streaming parser keeps memory constant even for 500 MB payloads; Gson reads the entire tree into memory, which can cause OOM. Choose based on your payload profile, not just hype.
- Jackson: annotation-driven, streaming, supports YAML+XML+JSON, powerful but complex configuration
- Gson: reflection-driven, flat config, minimal boilerplate, limited type adaptation
- Choose Jackson for large enterprise apps with complex serialization rules
- Choose Gson for simple REST clients, mobile apps, or when you need fast ramp-up
Serialization and Deserialization Patterns
Serialization converts Java objects to JSON strings; deserialization does the reverse. Both Jackson and Gson use reflection to map field names to JSON keys by default. You can override names with @JsonProperty (Jackson) or @SerializedName (Gson). Always consider null handling: Jackson serializes nulls by default; Gson omits them unless you configure serializeNulls(). For deserialization, missing fields in JSON are simply left as null in the object — but if you have a constructor with required parameters, Jackson may fail. Use @JsonCreator to mark a constructor, or Gson's InstanceCreator for custom logic. A common pattern: create a DTO with @JsonProperty annotations and a no-arg constructor, then validate with Bean Validation after deserialization.
Handling Nested Objects and Collections
JSON frequently contains nested objects and arrays. Both Jackson and Gson handle this automatically as long as the Java class has matching nested types. For example, an order containing a list of line items. The key challenge is generic types — Jackson needs TypeReference to preserve generic information; Gson uses TypeToken. Without these, deserialization produces a List<Map> instead of List<LineItem>, leading to ClassCastException later. Jackson's ObjectMapper.convertValue() can help but it's slower. Real tip: when your JSON has arrays of objects, always use TypeToken or TypeReference. Don't rely on the compiler's type inference — it won't save you at runtime.
Custom Serialization: Dates, Nulls, and Polymorphism
Real-world JSON often requires custom handling: date formats, null behaviour, and polymorphic types (e.g., a List<Animal> where each element could be Dog or Cat). Jackson provides @JsonTypeInfo and @JsonSubTypes for polymorphic deserialization. Gson requires a RuntimeTypeAdapterFactory (a third-party extension or manual implementation). Date formatting is another common issue — both libraries default to timestamps or ISO formats. Configure explicitly to match your API contract (e.g., "2026-04-22T15:30:00Z"). For null handling in collections, Jackson can omit null entries via @JsonInclude(Include.NON_NULL). A production pattern: write a custom serializer for any type that changes version often — this gives you control over backward compatibility.
- The JSON must include a discriminator field (e.g., "type") to tell the deserializer which subclass to use
- Jackson's @JsonTypeInfo lets you choose where the discriminator lives (property, wrapper, etc.)
- Gson lacks built-in polymorphic support — use RuntimeTypeAdapterFactory or write a custom deserializer
- If you don't configure polymorphism, deserialization of an abstract type fails with an error
Common JSON Mistakes in Production
Even experienced developers make recurring JSON mistakes. Three that appear in almost every code review: (1) Using the wrong library's annotations — e.g., Gson's @Expose on a Jackson project. (2) Forgetting to register Java 8 modules for LocalDate, Optional, etc. (3) Assuming null fields behave the same way across versions. Jackson 2.x changed its default null serialization behavior between minor versions — a fact that has burned many teams during upgrades. Another mistake: not testing serialization output changes when upgrading the library. CI should include a golden JSON file that every build compares against.
Silent Null Field Loss: Gson's @Expose vs Jackson's @JsonInclude
GsonBuilder.serializeNulls(). Jackson had @JsonInclude(Include.ALWAYS) on the class, but Gson doesn't recognize Jackson annotations.GsonBuilder().serializeNulls().create() to the Gson instance, and remove the Jackson-specific annotation. Then add a unit test that asserts null fields are present in the JSON.- Annotations from one library don't apply to the other — always verify default behaviors.
- Never assume JSON output matches what you see in the IDE debugger.
- Write contract tests that check the actual JSON string for every field, including nulls.
ObjectMapper().registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS). Gson: new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()Key takeaways
Common mistakes to avoid
6 patternsMemorising syntax before understanding the concept
Skipping practice and only reading theory
Mixing Jackson and Gson annotations
Forgetting to register JavaTimeModule for LocalDate
JavaTimeModule())Assuming no-arg constructor is optional
Not testing serialized output in CI
Interview Questions on This Topic
Explain the difference between Jackson's ObjectMapper and Gson's Gson class.
Frequently Asked Questions
That's Java I/O. Mark it forged?
4 min read · try the examples if you haven't