Category Archives: Jackson

Jackson 3 vs Jackson 2: Complete Comparison of API Changes, Performance, and New Features

The first time I ran a Jackson 3.1.2 build against a codebase that had been on Jackson 2 since 2019, the compile output had three categories of error: removed methods I expected, removed methods I had completely forgotten about, and one that genuinely surprised me — a custom deserialiser that silently stopped working because it depended on mutable ObjectMapper reconfiguration after injection. Jackson 3 is not an incremental release. It raises the Java baseline to 17, renames the entire package namespace, makes mapper configuration immutable, inlines three modules that previously required separate dependencies, and removes the APIs most responsible for serialisation CVEs. The changes below are the ones that actually matter when you’re deciding whether to upgrade — not a spec recap, but the differences that will touch your code.

The Change That Will Actually Break Your Code First

Before the full comparison table: in every Jackson 3 upgrade I have seen, the first breaking change that isn’t a compile error is the mapper immutability model. If anywhere in your codebase you have code that retrieves an ObjectMapper bean and calls a configure method on it after instantiation — even once, in a test setup — that code is silently a no-op in Jackson 3. The mapper built by JsonMapper.builder().build() is locked. The call succeeds, nothing throws, but your configuration change has no effect. Tests pass. Production behaves differently. Audit every location where ObjectMapper is touched after construction before bumping the version.

At a Glance: Jackson 2 vs Jackson 3

FeatureJackson 2.x (2.17.2)Jackson 3.x (3.1.2)
Java baselineJava 8+Java 17+ (all core modules)
Package namespacecom.fasterxml.jackson.*tools.jackson.* (annotations stay at com.fasterxml.jackson.annotation)
Maven Group IDcom.fasterxml.jackson.coretools.jackson.core (annotations: com.fasterxml.jackson.core)
Primary mapper classObjectMapperJsonMapper (builder); ObjectMapper still present
Mapper constructionMutable: new ObjectMapper() + chained settersImmutable: JsonMapper.builder().build()
Exception hierarchyJsonProcessingException extends IOException (checked)JacksonException extends RuntimeException (unchecked)
Java recordsSupported from 2.12 via jackson-module-parameter-namesNative, zero extra dependency
Optional<T>Requires jackson-datatype-jdk8Built into core
java.time.* typesRequires explicit JavaTimeModule registrationAuto-registered
enableDefaultTyping()Deprecated in 2.10, removed in 2.16Does not exist — compile error
Security postureGadget attacks possible with misconfigurationTighter by default; removed dangerous APIs
Continue reading Jackson 3 vs Jackson 2: Complete Comparison of API Changes, Performance, and New Features

Jackson 2 to Jackson 3 Migration Guide: Step-by-Step Upgrade for Java Projects

When we upgraded the Jackson version in a Spring Boot 3 microservice last quarter, the build broke in ways I did not expect — not from the obvious API removals, but from a subtle interaction between our custom deserialiser and the new builder-based immutability model. The compile errors were clear enough. The runtime surprises were not. Jackson 3 drops three things that large codebases tend to lean on quietly: mutable mapper configuration, implicit module registration, and the enableDefaultTyping() escape hatch that became a security liability. If you have any of those in your codebase, this guide maps each one to its exact Jackson 3 replacement — with before/after code and the specific error message you will see if you miss it. Everything below was tested on jackson-databind 3.1.2, Spring Boot 3.4.1, and Java 21.0.3 (Eclipse Temurin) on an Apple M2 running macOS 15.3. If your stack differs significantly — particularly if you are on a Java 8 or 11 baseline — see the “Do Not Migrate Yet If” section before proceeding.

Do Not Migrate Yet If…

Before touching a single dependency version, run through this checklist. Jackson 3 is not a drop-in upgrade for every project, and a failed mid-migration rollback is significantly more painful than waiting.

Your Java baseline is below 11. Jackson 3 will not compile on Java 8 or Java 9. The minimum is Java 11, and the ergonomics (records, Optional support) are genuinely better on Java 17+. If you are blocked on Java 8 for contractual or infrastructure reasons, stay on Jackson 2.17.x — it receives security patches and is supported.

A key third-party library has not yet published a Jackson 3-compatible version. Before bumping jackson.version, run mvn dependency:tree | grep jackson and identify every transitive Jackson consumer. Check each library’s changelog for explicit Jackson 3 support. A common hidden blocker: older versions of Quarkus REST, Dropwizard, and some Kafka serialisers pull in Jackson 2 transitively and will break silently under a version mismatch.

Your codebase uses enableDefaultTyping() heavily and you cannot audit every call site before the sprint ends. This method does not exist in Jackson 3 — the build fails immediately. If you have dozens of occurrences spread across shared libraries owned by other teams, negotiate the migration timeline before starting. A partial migration with enableDefaultTyping() still present cannot compile against Jackson 3 at all.

What Changed at a Glance

AreaJackson 2.xJackson 3.x
Java baselineJava 8Java 11 (minimum)
Primary mapper classObjectMapperJsonMapper (builder-based); ObjectMapper still exists
Mapper constructionnew ObjectMapper()JsonMapper.builder().build()
Records supportRequires jackson-module-parameter-names from 2.12Native — zero extra module needed
Optional<T> supportRequires jackson-datatype-jdk8Built into core
JavaTimeModuleExplicit registration requiredAuto-registered by default
enableDefaultTyping()Deprecated in 2.10, removed in 2.16Completely absent — compile error
Group ID / packagescom.fasterxml.jacksontools.jackson — only jackson-annotations stays at com.fasterxml.jackson
Exception hierarchyJsonProcessingException extends IOException (checked)JacksonException extends RuntimeException (unchecked)
Continue reading Jackson 2 to Jackson 3 Migration Guide: Step-by-Step Upgrade for Java Projects

Jackson with Java Records, Optionals, and Sealed Classes (Java 21+)

Modern Java — particularly Java 16 through 21 — introduced several language features that change how you model data: Records for immutable value objects, Optional for nullable return types, and Sealed Classes for closed type hierarchies. Each of these interacts with Jackson in ways that require specific setup. This guide shows you exactly what to configure so that Jackson handles all three correctly.

Jackson with Java Records

Java Records are ideal DTOs: they are immutable, concise, and carry their own equals(), hashCode(), and toString() implementations. Jackson 3 supports Records natively with zero additional modules — the canonical constructor is used automatically for deserialisation and record accessor methods replace getters for serialisation.

Define a Record:

// A concise, immutable data transfer object
public record ProductRecord(
    Long   productId,
    String productName,
    double unitPrice
) {}
Continue reading Jackson with Java Records, Optionals, and Sealed Classes (Java 21+)

Jackson ObjectMapper: The Complete Guide to Reading and Writing JSON in Java

Jackson is the most widely used JSON library in the Java ecosystem, and ObjectMapper is its workhorse. Whether you need to convert a Java object into a JSON string, parse a JSON file from disk, or read from an HTTP response body, ObjectMapper handles it all with a clean, consistent API. This guide covers everything you need to use Jackson’s ObjectMapper effectively in real projects.

What Is Jackson ObjectMapper?

ObjectMapper is the central class in Jackson’s data-binding API. It bridges the gap between Java objects (POJOs) and JSON text. Internally it uses a JsonParser for reading and a JsonGenerator for writing, but you rarely interact with those directly — ObjectMapper wraps them behind a higher-level API.

Three key facts to keep in mind:

  • Thread-safe after configuration. Create one instance, share it across your application. Recreating it per request is expensive.
  • Highly configurable. Date formats, null handling, unknown-property behaviour, and much more are controlled via feature flags.
  • Extensible. Custom serialisers, deserialisers, and modules let you handle any type Jackson does not know about out of the box.

Adding Jackson to Your Project

The jackson-databind artifact is all you need. It pulls in jackson-core and jackson-annotations as transitive dependencies.

Continue reading Jackson ObjectMapper: The Complete Guide to Reading and Writing JSON in Java

Jackson Annotations Cheat Sheet: Every Annotation You Need With Examples

Jackson annotations give you precise control over how your Java objects map to JSON and back. Rather than relying solely on field names, you can rename properties, ignore specific fields, handle nulls, format dates, and much more — all without writing a single custom serialiser. This guide covers every annotation you will encounter in real projects, with concrete examples for each.

@JsonProperty — Rename a Field in JSON

Use @JsonProperty when the JSON key must differ from the Java field name — for example, when consuming a third-party API that uses snake_case.

public class OrderSummary {
    @JsonProperty("order_id")        // JSON key: order_id
    private Long orderId;

    @JsonProperty("customer_name")   // JSON key: customer_name
    private String customerName;

    // Getters and setters
}
// Serialise
{"order_id":1001,"customer_name":"Alice"}

// Deserialise: JSON with "order_id" maps correctly to the orderId field
Continue reading Jackson Annotations Cheat Sheet: Every Annotation You Need With Examples

Jackson Custom Serialisers, Deserialisers, and Mix-in Annotations: The Advanced Toolkit

There are two scenarios where Jackson’s built-in annotations are not enough: when you need to control exactly how a complex type is serialised, and when the class you need to annotate belongs to a third-party library whose source you cannot modify. For the first case, Jackson provides custom serialisers and deserialisers. For the second, it provides Mix-in Annotations — a mechanism that lets you attach annotations to any class without touching its source.

Writing a Custom Serialiser

Extend StdSerializer<T> and override serialize(). The method receives the value to write and a JsonGenerator you use to emit JSON tokens.

Suppose you have a Money type that should serialise as a structured JSON object containing the amount and the currency code:

public class Money {
    private final BigDecimal amount;
    private final String     currencyCode;
    // Constructor and getters
}
Continue reading Jackson Custom Serialisers, Deserialisers, and Mix-in Annotations: The Advanced Toolkit

Jackson Polymorphic Deserialisation: Handling Inheritance Hierarchies with @JsonTypeInfo

Polymorphic deserialisation is the technique of deserialising a JSON payload into one of several possible concrete subtypes based on information embedded in the JSON itself. It is essential whenever your API works with inheritance hierarchies — event systems, payment processors, notification pipelines, or any domain where a list of heterogeneous objects needs to round-trip through JSON. Jackson handles this cleanly with @JsonTypeInfo and @JsonSubTypes.

The Problem: Deserialising an Abstract Type

Consider an API that returns a list of payment methods. Some are credit cards, others are bank transfers. Both share a common base, but each has its own fields. Without polymorphic support, Jackson cannot decide which concrete class to instantiate when it sees a generic PaymentMethod type.

Setting Up the Hierarchy with @JsonTypeInfo and @JsonSubTypes

Annotate the base class (or interface) with @JsonTypeInfo to tell Jackson where the type discriminator lives in the JSON, and with @JsonSubTypes to register the permitted subtypes:

@JsonTypeInfo(
    use      = JsonTypeInfo.Id.NAME,    // Use a logical name as the discriminator
    include  = JsonTypeInfo.As.PROPERTY, // Embed it as a field in the JSON object
    property = "paymentType"             // The JSON key that carries the type name
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = CreditCardPayment.class, name = "credit_card"),
    @JsonSubTypes.Type(value = BankTransferPayment.class, name = "bank_transfer")
})
public abstract class PaymentMethod {
    private Long   paymentId;
    private double amountDue;
    // Getters and setters
}
Continue reading Jackson Polymorphic Deserialisation: Handling Inheritance Hierarchies with @JsonTypeInfo