What’s New in Jackson 3? A Complete Migration Guide

This article explains the major breaking changes introduced in Jackson 3—including the JDK 17 baseline, package and groupId renames, immutable JsonMapper, unchecked exceptions, default configuration shifts, performance tweaks, and module consolidation—while providing step‑by‑step migration advice and a quick‑reference table for developers upgrading from Jackson 2.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
What’s New in Jackson 3? A Complete Migration Guide

JDK Baseline Upgrade

Jackson 3.x requires Java 17 or newer, whereas Jackson 2.x runs on Java 8. The higher baseline enables use of Records, Sealed Classes, Pattern Matching and other Java 17 language features. Projects still on JDK 8 or 11 must upgrade the JDK before adopting Jackson 3; Spring Boot 3.x already mandates JDK 17, so new Spring Boot 4 projects satisfy this requirement out of the box.

Package and Maven groupId Changes

The Maven groupId and Java package prefixes were renamed:

Maven groupId : com.fasterxml.jacksontools.jackson Java package : com.fasterxml.jackson.xxxtools.jackson.xxx Annotation package : remains com.fasterxml.jackson.annotation, allowing Jackson 2 and Jackson 3 to coexist in the same classpath.

Core annotations such as @JsonSerialize and @JsonDeserialize move to tools.jackson.databind.annotation. Example imports:

// Jackson 2.x imports
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonParser;

// Jackson 3.x imports
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.core.JsonParser;

Immutable JsonMapper

In Jackson 2, ObjectMapper is mutable and can be reconfigured at any time, which is unsafe in multithreaded environments. Jackson 3 forces a builder pattern that produces an immutable JsonMapper:

// Jackson 2.x – mutable configuration
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

// Jackson 3.x – immutable builder
JsonMapper mapper = JsonMapper.builder()
    .enable(SerializationFeature.INDENT_OUTPUT)
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .build();

Creating format‑specific mappers now requires the matching subclass (e.g., YAMLMapper.builder() for YAML). The old new ObjectMapper(new YAMLFactory()) style is prohibited.

Exception Hierarchy Refactor

Jackson 2 uses checked JsonProcessingException and its subclasses. Jackson 3 replaces them with unchecked JacksonException and related types: JsonProcessingException

JacksonException
JsonParseException

StreamReadException
JsonEOFException

UnexpectedEndOfInputException Consequently, existing try‑catch blocks that catch JsonProcessingException must be updated, or the catch can be omitted because the new exceptions are runtime.

// Jackson 2.x – must handle checked exception
try {
    User user = mapper.readValue(json, User.class);
} catch (JsonProcessingException e) {
    // handle
}

// Jackson 3.x – unchecked, optional handling
User user = mapper.readValue(json, User.class);
// JacksonException may be thrown at runtime

Default Configuration Overhaul

Date Serialization

Jackson 2 serializes dates as numeric timestamps; Jackson 3 defaults to ISO‑8601 strings. To retain timestamp output, enable the legacy feature:

JsonMapper mapper = JsonMapper.builder()
    .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
    .build();

Primitive Null Handling

The feature FAIL_ON_NULL_FOR_PRIMITIVES changes default from false to true. Null values for primitive fields now cause a JsonMappingException. Resolve by using wrapper types (e.g., Integer instead of int) or by disabling the feature.

public class User {
    private int age; // Jackson 2: null → 0, Jackson 3: null → exception
}

Month Indexing

Feature DateTimeFeature.ONE_BASED_MONTHS switches default from false (0‑based months) to true (1‑based). Code that relied on “0 = January” must be reviewed.

Performance and Memory Optimizations

BeanDescription is now supplied via a Supplier, enabling lazy loading and reducing temporary object creation.

The core RecyclerPool initializes lazily, improving memory efficiency in high‑throughput scenarios. TokenStreamFactory.Feature.INTERN_FIELD_NAMES defaults to false, preventing automatic interning of field name strings and lowering heap pressure.

Module Consolidation

Three modules required for Java 8 features in Jackson 2 ( jackson-module-parameter-names, jackson-datatype-jdk8, jackson-datatype-jsr310) are now bundled inside jackson-databind. No separate dependencies are needed.

Pros, Cons, and Suitable Scenarios

Advantages

Immutable JsonMapper guarantees thread safety.

Full support for modern Java language features.

Safer defaults: ISO‑8601 dates, stricter null handling, stricter polymorphic type validation.

Performance gains from lazy loading and optimized pools.

Reduced dependency footprint.

Drawbacks

Jackson 3.0 is a transition release; 3.1 is the first LTS version.

Numerous breaking changes require careful migration.

Many third‑party libraries still depend on Jackson 2.

Recommended Scenarios

New projects using Spring Boot 4 – Jackson 3 is the default.

High‑concurrency systems that need thread‑safe JSON handling.

Projects that want to adopt Java 17 features such as Records and Sealed Classes.

Legacy projects upgrading to Spring Boot 4 should plan migration steps and may use the coexistence mode.

Migration Checklist

Upgrade the JDK to 17 or newer.

Upgrade Spring Boot to 4.x (which bundles Jackson 3).

Run OpenRewrite’s Jackson 2→3 recipe:

mvn rewrite:run -DactiveRecipes=org.openrewrite.java.jackson.UpgradeJackson_2_3

Optionally enable spring.jackson.use-jackson2-defaults as a temporary safety net.

Replace all com.fasterxml.jackson imports with tools.jackson equivalents and switch object creation to the builder pattern.

Update exception handling from JsonProcessingException to JacksonException (or remove catches if not needed).

Verify date‑format expectations with front‑end consumers; enable WRITE_DATES_AS_TIMESTAMPS if timestamps are required.

Review primitive fields for null handling; use wrapper types or disable FAIL_ON_NULL_FOR_PRIMITIVES.

Check any month‑related logic for the shift to 1‑based months.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMigrationperformanceJSONSpring Bootjackson
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.