Let's get started with a Microservice Architecture with Spring Cloud:
What’s New in Jackson 3?
Last updated: September 10, 2026
1. Overview
Jackson has been the most popular JSON processing library in the Java ecosystem for over a decade. After years of incremental updates in the 2.x line, the release of Jackson 3.0 introduces a major architectural overhaul designed to address long-standing technical debt and modernize its API.
In this tutorial, we’ll explore key changes in Jackson 3, how they impact our existing codebases, and the steps required to migrate from the 2.x line.
2. Technical Prerequisites and Migration Path
Let’s review the foundational changes in Jackson 3, including minimum requirements and the necessary steps to prepare our project for migration.
2.1. Java 17 Baseline
Jackson 3.0 raises its minimum supported JDK version from Java 8 to Java 17. This allows Jackson to natively use modern Java language features, such as Java Records or pattern matching.
Before migrating to Jackson 3.0, we should ensure that our project meets this requirement. If not, we must consider upgrading the Java version in our project or remaining on the Jackson 2.x line.
2.2. Artifacts and GroupId
The new major Jackson version introduces a completely new groupId for its artifacts – tools.jackson.core, replacing com.fasterxml.jackson.core. This change allows Jackson 2.x and 3.x to coexist on the classpath in parallel during gradual migrations.
To install the new version of the library, we add it to our pom.xml:
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.1</version>
</dependency>
Alternatively, if we want to add a dedicated mapper for another format, such as XML, we need to include its specific module:
<dependency>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>3.2.1</version>
</dependency>
For these format-specific modules, the group is tools.jackson.dataformat. It’s also worth noting that the jackson-annotations dependency still remains under the old com.fasterxml.jackson.core group. This exception ensures that our core domain models with Jackson annotations do not require modifications when upgrading.
2.3. Package Name Changes
Because of the artifacts groupId change, the base Java package has also shifted from com.fasterxml.jackson to tools.jackson. When migrating projects, we need to update our import statements:
// Remove Old Jackson 2.x imports
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
// Use New Jackson 3.x imports
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.JsonNode;
2.4. Android Compatibility
Because Jackson 3 relies entirely on Java 17, Android projects must be properly configured to support these language features.
Android developers should to target API level 34 (Android 14) or enable core library desugaring in their build configuration to use Jackson 3.
2.5. Removal of Deprecated Methods
As part of the major version bump, Jackson 3 removes all classes, methods, and configurations that had the @Deprecated annotation in the 2.x line. If our current codebase uses any legacy methods, we must replace them with 3.x alternatives before the migration.
Now, let’s move on to specific functional changes in Jackson 3.
3. Immutable ObjectMapper and the Builder Pattern
In previous versions, ObjectMapper instances were mutable. By modifying configurations or registered modules on an active mapper after initialization, we risked running into thread-safety issues, such as race conditions when concurrent threads tried to read and alter the mapper’s state simultaneously.
Jackson 3 solves this by enforcing strict immutability for its mappers. It removes direct configuration methods on ObjectMapper. Instead, we use the builder pattern and format-specific builders to configure our mappers before instantiation.
3.1. Building a JsonMapper
Let’s see how to construct and configure a JsonMapper using the available API:
JsonMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(JsonWriteFeature.ESCAPE_NON_ASCII)
.configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.build();
Here, we initialize a builder for the JSON format. We enable pretty-printing, disable the escaping of non-ASCII characters, and tell Jackson not to serialize dates as numeric timestamps.
Jackson uses Java enums as sets of feature toggles to control parsing, serialization, and mapping behavior. We can find general configuration options in the SerializationFeature, DeserializationFeature, and MapperFeature enums. For format- or type-specific requirements, Jackson provides dedicated enums such as JsonWriteFeature (for JSON generation) and DateTimeFeature (for date handling).
3.2. Copying Mappers
If we need a slightly modified version of an existing mapper, we can use the rebuild() method:
JsonMapper prettyMapper = mapper.rebuild()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
Note that prettyMapper and mapper from the previous snippet are completely different objects. The rebuild() method copies the internal configuration of the original mapper into a new builder, allowing us to spawn a new instance with slight modifications. Because the mappers are immutable, this operation is entirely thread-safe and won’t affect the original mapper.
3.3. Support for Alternative Data Formats
If our project requires a different data format, we simply switch to the corresponding format-specific builder. For example, we can configure an XmlMapper for XML or a YAMLMapper for YAML using their respective builders:
XmlMapper xmlMapper = XmlMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
YAMLMapper yamlMapper = YAMLMapper.builder()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.build();
4. Unchecked Exceptions
One of the biggest readability issues in Jackson 2.x was dealing with the checked JsonProcessingException and IOException during serialization and deserialization. Supporting them inside Java Streams or lambda expressions often led to verbose try-catch blocks or custom wrapper utilities.
In Jackson 3, JacksonException replaces JsonProcessingException as the root exception, and it now extends RuntimeException:
List<User> users = jsonPayloads.stream()
.map(json -> mapper.readValue(json, User.class)) // No try-catch needed!
.toList();
This change significantly cleans up our stream operations. Because the exception is unchecked, the compiler no longer forces us to handle it immediately. We can omit the clutter of try-catch blocks within our lambdas, resulting in much cleaner code.
5. Built-in Java 8+ Features
Historically, to properly handle Java 8 features like java.time classes, java.util.Optional, or constructor parameter name detection, we had to explicitly include and register three separate modules: jackson-datatype-jsr310, jackson-datatype-jdk8, and jackson-module-parameter-names.
Since Jackson 3 targets Java 17, these three modules are now integrated into jackson-databind. We can serialize and deserialize LocalDateTime and Optional properties immediately upon creating a new mapper:
public record Event(String title, Optional<String> description, LocalDateTime eventDate) {}
// ...
Event event = new Event("Tech Talk", Optional.of("Jackson 3 overview"), LocalDateTime.now());
JsonMapper mapper = JsonMapper.builder().build();
String json = mapper.writeValueAsString(event);
Event deserialized = mapper.readValue(json, Event.class);
This native support significantly simplifies the codebase when working with Java 8+ types, as it requires no additional configuration.
6. Configuration Defaults and Behavioral Changes
Jackson 3 changes several default settings to reflect modern development practices and community feedback. A full list of changes can be found in the official documentation. We describe the most impactful ones.
6.1. FAIL_ON_UNKNOWN_PROPERTIES Defaults to false
In Jackson 2.x, encountering unknown JSON fields during deserialization threw an UnrecognizedPropertyException by default unless we explicitly disabled it on the mapper.
In Jackson 3.0, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES defaults to false. Jackson safely ignores unrecognized fields in incoming JSON payloads unless we explicitly set this flag to true. Let’s verify this:
record User(String name) {}
// ...
JsonMapper mapper = JsonMapper.builder().build();
// The JSON contains "unknownField" which is not present in the User class
String json = "{\"name\":\"Alice\", \"unknownField\":\"ignored_value\"}";
// This executes without throwing an exception
User user = mapper.readValue(json, User.class);
In some cases such as strict API validations, it may be necessary to throw an exception when encountering an unknown field. To achieve this, we reenable the feature on the builder:
JsonMapper strictMapper = JsonMapper.builder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
User strictUser = strictMapper.readValue(json, User.class);
This now throws an UnrecognizedPropertyException.
6.2. Fast Floating-Point Operations
Jackson 3 enables fast floating-point parsing and writing algorithms by default, significantly increasing performance when processing heavy numerical payloads.
In the late Jackson 2.x releases, we had to manually activate this behavior by configuring the factory features StreamReadFeature.USE_FAST_DOUBLE_PARSER:
JsonFactory.builder()
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
.build()
In Jackson 3, we benefit from this performance boost right out of the box.
6.3. Simplified Date and Time Formatting
Jackson 3 introduces DateTimeFeature, consolidating date and time configuration options across standard JDK dates, Java 8 java.time, and Joda-Time into a single, unified feature set:
JsonMapper mapper = JsonMapper.builder()
.enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
We can now format dates globally across all date types.
6.4. Standard Bean Naming
Jackson 3 bakes standard bean naming into its core engine as the permanent default behavior. Because this behavior is no longer optional, Jackson 3 removed the MapperFeature.USE_STD_BEAN_NAMING configuration flag entirely.
Now, we get more consistent property naming out of the box, as we can derive property names from standard getters and setters. Consider a Java class with a getter getTheURL():
class Link {
private String address;
public Link(String address) {
this.address = address;
}
public String getTheURL() {
return address;
}
}
// ...
String json = mapper.writeValueAsString(new Link("https://baeldung.com"));
Note that Jackson derives JSON property names from getters instead of private fields. In this example, the getter getTheURL() determines the output key theURL:
{"theURL":"https://baeldung.com"}
The backing private field address isn’t considered.
7. Enhancements to the JsonNode Tree Model
The JsonNode tree model remains crucial for working with unstructured data. In Jackson 3, the API underwent several structural updates to make traversing JSON trees safer and more intuitive.
First, the node trees now expose dedicated subtypes, such as ObjectTreeNode and ArrayTreeNode. This shift provides better type-safety and clearer intentions when manipulating complex JSON structures, compared to checking node types on a generic JsonNode interface.
The API also introduces safer method names to align with Java conventions. For example, JsonParser.getText() has been renamed to getString() to avoid ambiguity and standardize the naming scheme.
Finally, Jackson 3 introduces optional value accessors, such as intValueOpt(), asInt(), and required(). They eliminate the need for manual null checking and provide a much cleaner approach to extracting data.
Here’s an example showing all the three updates:
String json = "{\"product\":\"Laptop\",\"price\":1200}";
JsonNode root = mapper.readTree(json);
String product = root.get("product").asString();
OptionalInt price = root.get("price").intValueOpt();
In this snippet, we parse a raw JSON string into a tree model. We then safely navigate the structure using the get() method and extract typed values using the new asString() and intValueOpt() accessors. This approach bypasses the clunky type-checking and manual casting that were often required in previous versions.
8. Conclusion
In this article, we explored the major architectural and functional changes introduced in Jackson 3.0. By raising the baseline to Java 17, the library drops years of technical debt and natively embraces modern Java features like Records, java.util.Optional, and the java.time API without the need for external modules.
The shift toward strict immutability via the builder pattern successfully resolves historic thread-safety issues, while the transition to unchecked exceptions greatly improves developer readability, especially when working with modern Java Streams. Additionally, smarter configuration defaults and Tree Model enhancements make everyday JSON processing cleaner and more intuitive.
While migrating from the 2.x line requires some work, the long-term benefits of a faster, safer, and more robust API make the upgrade well worth the effort.
As always, the complete source code for all the examples is available over on GitHub.
















