Category Archives: Jackson

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

Jackson Streaming API and JsonNode Tree Model: High-Performance JSON Processing in Java

For most applications, ObjectMapper.readValue() and writeValueAsString() are all you need. But when JSON files reach hundreds of megabytes, or when you need to work with dynamic JSON whose schema you do not know at compile time, Jackson offers two lower-level APIs that give you full control: the Streaming API (JsonParser / JsonGenerator) and the Tree Model (JsonNode). This guide explains when to use each and shows practical examples for both.

The Streaming API: Parsing Gigabyte-Sized JSON Files

The Streaming API processes JSON one token at a time. It never loads the entire document into memory, making it the only practical choice for very large files. The trade-off is verbosity — you must walk the token stream yourself.

Reading a Large JSON Array with JsonParser

Suppose you have a file with millions of log entries and you only need to extract records that match a filter condition:

JsonFactory jsonFactory = new JsonFactory();
int errorCount = 0;

try (JsonParser parser = jsonFactory.createParser(new File("large-logs.json"))) {

    // Confirm the root is an array
    if (parser.nextToken() != JsonToken.START_ARRAY) {
        throw new IllegalStateException("Expected a JSON array at the root");
    }

    // Walk each element in the array
    while (parser.nextToken() != JsonToken.END_ARRAY) {

        String logLevel = null;
        String logMessage = null;

        // Walk each field inside the current object
        while (parser.nextToken() != JsonToken.END_OBJECT) {
            String fieldName = parser.getCurrentName();
            parser.nextToken(); // Move to field value

            if ("level".equals(fieldName)) {
                logLevel = parser.getText();
            } else if ("message".equals(fieldName)) {
                logMessage = parser.getText();
            }
            // All other fields are skipped automatically
        }

        if ("ERROR".equals(logLevel)) {
            System.out.println("ERROR: " + logMessage);
            errorCount++;
        }
    }
}
System.out.println("Total errors found: " + errorCount);
Continue reading Jackson Streaming API and JsonNode Tree Model: High-Performance JSON Processing in Java

Jackson Security Best Practices: Defending Against Deserialization Gadget Attacks

Jackson is powerful, but some of its older configuration options can open serious security vulnerabilities in your application. The most critical is polymorphic type handling: when configured carelessly, it allows an attacker to supply a JSON payload that causes Jackson to instantiate and invoke arbitrary Java classes on your server — a Remote Code Execution (RCE) vector known as “deserialization gadget attacks.” This guide explains the risk, shows you what to avoid, and demonstrates the safe patterns you should follow in all production code.

The Risk: enableDefaultTyping and Gadget Attacks

Jackson historically provided a method called enableDefaultTyping() that automatically embedded and honoured Java class names in serialised JSON. This means an attacker who can send JSON to your application could supply a class name of their choosing as the type discriminator, causing Jackson to instantiate that class during deserialisation.

The attack exploits classes already on the JVM classpath that have side effects in their constructors or setters — so-called “gadget classes.” Common culprits include JDBC drivers, logging libraries, and many Apache Commons classes.

Never use this in production:

// DANGEROUS — do not use in any application that receives untrusted JSON
mapper.enableDefaultTyping(); // Deprecated and removed in Jackson 2.16
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // Also dangerous

Both of these methods were deprecated in Jackson 2.10 and completely removed in Jackson 3.x. If you encounter them in a codebase being migrated to Jackson 3, treat it as a blocker — the code will not compile and the security risk must be remediated before upgrading.

Continue reading Jackson Security Best Practices: Defending Against Deserialization Gadget Attacks