Jackson is the de facto standard for JSON processing in Java. It ships as the default JSON library in Spring Boot, is used by thousands of open-source frameworks, and handles everything from trivial string serialisation to streaming gigabyte-sized files. If you write Java and you touch JSON — which is essentially everyone — you will use Jackson. This post gives you the essential foundation: why Jackson dominates, how to set it up, and the one architectural rule that matters most.
Why Jackson, Not Gson?
Both Jackson and Gson are mature, actively maintained libraries. The key differences are:
| Feature | Jackson | Gson |
|---|---|---|
| Spring Boot integration | Built-in default (zero config) | Requires manual registration |
| Performance | Faster on most benchmarks, especially large payloads | Competitive on small payloads |
| Streaming API | Yes — JsonParser / JsonGenerator | No |
| Java 8+ type support | Via JavaTimeModule, Jdk8Module | Limited, requires custom adapters |
| Polymorphic types | First-class with @JsonTypeInfo | Requires manual type adapters |
| Module ecosystem | Large (YAML, XML, CSV, CBOR, etc.) | Minimal |
For greenfield projects on Spring Boot or Jakarta EE, Jackson is the correct default choice. Gson remains a reasonable option for Android or lightweight standalone utilities.
Maven and Gradle Setup
Maven:
<!-- Core: data binding, annotations, streaming -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.1.2</version>
</dependency>
<!-- Java 8 date/time support (LocalDate, Instant, etc.) -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>3.1.2</version>
</dependency>
Gradle:
implementation 'com.fasterxml.jackson.core:jackson-databind:3.1.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:3.1.2'
If you are using Spring Boot, both are included transitively via spring-boot-starter-web. You do not need to declare them explicitly unless you need to override the version.
The ObjectMapper Lifecycle — The One Rule That Matters Most
ObjectMapper is expensive to create. The first time you call readValue() or writeValueAsString() on a new type, Jackson inspects that type via reflection, builds an internal serialisation model, and caches it. On subsequent calls, the cached model is reused, making those calls fast.
Creating a new ObjectMapper per request throws away that cache and pays the reflection cost every single time. It also causes excessive garbage collection pressure under load.
Wrong — do not do this:
// BAD: creates a new ObjectMapper on every request
@PostMapping("/orders")
public ResponseEntity<OrderDto> createOrder(@RequestBody String rawJson) throws Exception {
ObjectMapper mapper = new ObjectMapper(); // New instance per request = performance problem
OrderDto order = mapper.readValue(rawJson, OrderDto.class);
return ResponseEntity.ok(order);
}
Correct — create once, share everywhere:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper sharedObjectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
ObjectMapper is thread-safe once configured. A single bean shared across your entire application is the correct pattern.
Your First Serialise/Deserialise Example
The following shows the complete round-trip: a ProductSummary object serialised to a JSON string, then deserialised back to a Java object. This pattern applies to any POJO with standard getters and setters.
public class ProductSummary {
private Long productId;
private String productName;
private double listPrice;
// Constructors, getters, setters
}
ObjectMapper mapper = new ObjectMapper();
// Serialise: Java object -> JSON string
ProductSummary product = new ProductSummary(1L, "Mechanical Keyboard", 79.99);
String jsonOutput = mapper.writeValueAsString(product);
System.out.println(jsonOutput);
// Output: {"productId":1,"productName":"Mechanical Keyboard","listPrice":79.99}
// Deserialise: JSON string -> Java object
ProductSummary restored = mapper.readValue(jsonOutput, ProductSummary.class);
System.out.println(restored.getProductName()); // Output: Mechanical Keyboard
Jackson’s Three Processing Models
Jackson offers three distinct APIs for different use cases:
- Data Binding (
ObjectMapper) — Converts JSON directly to/from POJOs. Use for 95% of all tasks. - Tree Model (
JsonNode) — Represents JSON as a navigable tree. Use for dynamic or schema-unknown JSON. - Streaming API (
JsonParser/JsonGenerator) — Token-level access with no in-memory document. Use for large file processing.
This series covers all three in detail.
Series Index
This post is the entry point for the complete Jackson 3 series. Each part builds on the previous — start here if you are new to Jackson, or jump directly to whichever topic you need.
- Part 0 (this post): Jackson 101 — Setup, ObjectMapper Lifecycle, and First Steps
- Part 1: Jackson ObjectMapper Complete Guide
- Part 2: Jackson with Java Records, Optionals, and Sealed Classes
- Part 3: Jackson Annotations Cheat Sheet
- Part 4: Custom Serialisers, Deserialisers, and Mix-ins
- Part 5: Polymorphic Deserialisation with @JsonTypeInfo
- Part 6: Streaming API and JsonNode Tree Model
- Part 7: Jackson Security and Best Practices
- Jackson 2 → Jackson 3 Migration Guide
- Jackson 3 vs Jackson 2: Complete Comparison
AI Prompts for Jackson 101 Setup
Scaffold JsonMapper Spring Bean
I am starting a new Spring Boot 3 project with Jackson 3.1.2. Generate a @Configuration class with a JsonMapper @Bean built using JsonMapper.builder(). The bean should disable WRITE_DATES_AS_TIMESTAMPS, set FAIL_ON_UNKNOWN_PROPERTIES to false, exclude null fields from serialisation, and be marked @Primary so Spring Boot’s HTTP message converters use it. Add a brief inline comment on each builder call explaining why that setting is applied.
What it does: Generates a complete, production-ready @Configuration class with a single immutable JsonMapper bean. Each builder call is annotated inline so the intent is clear to reviewers, and the @Primary marker ensures Spring Boot’s message converters use this bean without extra wiring.
When to use it: At project creation time to establish a single shared mapper before any serialisation code is written. Prevents the common anti-pattern of multiple ObjectMapper instances with inconsistent settings scattered across service classes.
Upgrade ObjectMapper to Jackson 3
I have this existing Jackson 2 @Configuration class: [paste your class here]. Migrate it to Jackson 3.1.2 using JsonMapper.builder(). Convert every setter-style call — setSerializationInclusion, configure, setVisibility — to its builder equivalent. Remove explicit registerModule calls for JavaTimeModule and Jdk8Module since they are auto-registered in Jackson 3. If enableDefaultTyping() is present, flag it as removed in Jackson 3 and suggest the @JsonTypeInfo replacement. Show before and after side by side.
What it does: Produces a line-by-line migration of your existing configuration class, converting mutable setter calls to immutable builder equivalents, removing redundant module registrations, and surfacing any removed APIs as clearly labelled blockers with their Jackson 3 safe replacements.
When to use it: When upgrading a Spring Boot application to Jackson 3.1.x and wanting a reviewer-friendly diff rather than a from-scratch rewrite. Especially useful when the configuration has grown through multiple contributors and contains a mix of feature flags, inclusion settings, and module registrations.
Jackson vs Gson Decision Guide
We are building a Spring Boot 3 microservice with Java 21 records as DTOs. Compare Jackson 3.1.2 and Gson 2.x across: Spring Boot auto-configuration support, Java 21 records and sealed classes support, module ecosystem breadth (YAML, CBOR, XML), serialisation performance on record types, security CVE history, and ongoing maintenance activity. Format the result as a comparison table followed by a one-paragraph recommendation suitable for an ADR.
What it does: Produces a structured comparison table with a concrete factual statement per cell, covering all six requested dimensions, followed by a recommendation paragraph that can be dropped directly into an Architecture Decision Record without editing.
When to use it: When justifying the Jackson choice to engineers from Android or Kotlin backgrounds where Gson is familiar, or when documenting the library selection rationale at the start of a new project.
Conclusion
Jackson is the clear choice for JSON in Java projects, particularly those running on Spring Boot. It delivers outstanding performance, a rich annotation model, and a broad module ecosystem. The one architectural principle to take away from this introduction is the ObjectMapper lifecycle: create it once, configure it at startup, and share it as a singleton. Everything else in this series builds on that foundation.