What’s New in Jackson 3?
Jackson 3 is a major release of the Jackson JSON processing library. Unlike a typical minor or patch upgrade, Jackson 3 introduces several API and behavioral changes that can affect existing Java applications. The most important changes include a Java 17 baseline, immutable ObjectMapper instances, builder-based configuration, unchecked Jackson exceptions, built-in support for several Java 8+ datatypes, changed configuration defaults, and improvements to the JsonNode tree model. Jackson 3.0 was released in October 2025. For applications starting a new Jackson 3 migration, Jackson 3.1 is the recommended long-term target.
1. What’s New in Jackson 3
Jackson has traditionally been one of the most widely used JSON libraries in the Java ecosystem. Jackson 2.x supported Java 8 for a long time and provided a mutable ObjectMapper that could be configured after construction. Jackson 3 takes a different approach. It modernizes the API around immutability, builders, Java 17, and more consistent format-specific APIs. The major changes can be summarized as follows:
- Java 17 is the minimum JDK version.
ObjectMapperand related core components are immutable.- Builder APIs are used for mapper configuration.
- Jackson exceptions are unchecked.
- Java 8 datatype modules are integrated into Jackson databind.
- Several configuration defaults have changed.
- The
JsonNodeAPI has improved null handling. - Several Jackson packages and core APIs have been renamed.
Jackson 3 is also not API-compatible with Jackson 2.x, so migration requires more than simply changing a dependency version.
2. Immutable ObjectMapper and Builder Pattern
One of the biggest architectural changes in Jackson 3 is that mapper instances are immutable after construction.
| Aspect | Jackson 2 | Jackson 3 |
|---|---|---|
| Mapper configuration | Mapper could be configured after creation | Configuration is defined through a builder before creation |
| Mapper mutability | ObjectMapper was commonly configured and modified | Mapper instances are immutable after construction |
| Recommended approach | Create mapper and configure it | Configure the builder and call build() |
| JSON mapper | ObjectMapper | JsonMapper |
| Sharing | Requires care when configuration can change | Configured mapper can be safely reused |
In Jackson 2, applications commonly created an ObjectMapper and then configured it:
ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
In Jackson 3, configuration should happen through a builder before the mapper is created:
ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
The result is an immutable mapper whose configuration cannot be changed accidentally by another part of the application. This also makes it easier to create a mapper once and safely reuse it across the application.
| Mapper | Purpose |
|---|---|
JsonMapper | JSON processing |
XmlMapper | XML processing |
YAMLMapper | YAML processing |
Jackson 3 also provides a rebuild mechanism when an existing mapper configuration needs to be used as the starting point for a new mapper.
3. Jackson 3 Java Example
The following example demonstrates Jackson 3 serialization and deserialization using an immutable Java record, along with built-in support for Optional and LocalDate.
// Jackson3Example.java
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.SerializationFeature;
import java.time.LocalDate;
import java.util.Optional;
public class Jackson3Example {
public record Employee(
int id,
String name,
Optional & lt; String & gt; department,
LocalDate joiningDate
) {}
public static void main(String[] args) {
ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
Employee employee = new Employee(
101,
"Amit",
Optional.of("Engineering"),
LocalDate.of(2026, 1, 15)
);
String json = mapper.writeValueAsString(employee);
System.out.println("Serialized JSON:");
System.out.println(json);
Employee restored =
mapper.readValue(json, Employee.class);
System.out.println();
System.out.println("Deserialized employee:");
System.out.println("Id: " + restored.id());
System.out.println("Name: " + restored.name());
System.out.println(
"Department: " +
restored.department().orElse("Not available")
);
System.out.println("Joining date: " + restored.joiningDate());
JsonNode root = mapper.readTree(json);
String name = root.at("/name").asText();
String department = root.at("/department").asText();
System.out.println();
System.out.println("Values from JsonNode:");
System.out.println("Name: " + name);
System.out.println("Department: " + department);
}
}
3.1 Code explanation
This example demonstrates several important Jackson 3 features in a single program. The imports use the Jackson 3 tools.jackson packages, while LocalDate and Optional demonstrate built-in support for modern Java types. The Employee record defines an immutable data model containing an ID, name, optional department, and joining date. The JsonMapper.builder() creates the mapper using the builder pattern, and INDENT_OUTPUT enables formatted JSON output before the mapper is built. An Employee object is then created and serialized into JSON using writeValueAsString(). The same JSON is deserialized back into an Employee object using readValue(), demonstrating both serialization and deserialization. Finally, readTree() parses the JSON into a JsonNode tree, and the at() method uses JSON Pointer expressions such as /name and /department to directly access individual values without creating another Java object. Overall, the example demonstrates Jackson 3’s builder-based mapper configuration, immutable Java records, built-in support for Optional and LocalDate, object-to-JSON conversion, JSON-to-object conversion, and flexible tree-based JSON processing.
3.2 Code Run and Output
When the program is executed, Jackson 3 first serializes the Employee object into formatted JSON. It then deserializes the JSON back into an Employee object and finally uses the JsonNode tree model to read the name and department fields. The output is:
Serialized JSON:
{
"id" : 101,
"name" : "Amit",
"department" : "Engineering",
"joiningDate" : "2026-01-15"
}
Deserialized employee:
Id: 101
Name: Amit
Department: Engineering
Joining date: 2026-01-15
Values from JsonNode:
Name: Amit
Department: Engineering
The output shows that the same Jackson 3 mapper can be used for serialization, deserialization, and tree-based JSON processing.
4. Conclusion
Jackson 3 is more than a simple dependency upgrade because it modernizes the Jackson API while making configuration safer and simpler. It requires Java 17, uses immutable mapper instances created through builders, changes Jackson exceptions to unchecked exceptions, and provides built-in support for common Java 8+ datatypes. It also introduces changes to several default behaviors and APIs, so existing applications should review their configuration and tests during migration. The JsonNode tree model continues to provide a flexible way to work with dynamic JSON while offering improved null handling. For new applications, these changes make Jackson 3 straightforward to adopt, while existing Jackson 2 applications should approach the upgrade as a migration by upgrading the JDK, updating dependencies and imports, moving mapper configuration to builders, reviewing exception handling, and testing behavior affected by changed defaults.

