← Back to list

Jackson 3.1.0 released

Jackson 3.1.0 was released 3 weeks ago (on February 23, 2026), bit over 4 months after 3.0.0 release (see this blog post about 3.0.0). One…

@cowtowncoder · 2026-03-15 03:03 · 65 claps · 7.3 min read
#java #json #jackson #open-source
Open on Medium ↗
Wiki topics: 🔓 · Open Source

Jackson 3.1.0 released

Jackson 3.1.0 was released 3 weeks ago (on February 23, 2026), bit over 4 months after 3.0.0 release (see this blog post about 3.0.0). One release candidate (3.1.0-rc1) was released before the GA version.

The “Big Fix” minor version

The most notable thing about this version is probably the sheer number of changes — mostly fixes, as usual — 143 in total (80 of them in jackson-databind). The number of changes is among the highest for any release, likely second only to the preceding 3.0.0 (164 listed changes).

Many of the issues resolved were quite old: three had been filed more than 10 years ago — oldest, [databind#221], dating back to May 2013! — and 30 of them 5+ years ago. So 3.1.0 can fairly be described as a bit of a “spring-cleaning” release.

The first 3.x Long-Term Support (LTS) version

One noteworthy thing is that 3.1 is the first LTS (see [JSTEP-13]) version in 3.x series. This means that it will be kept open longer than following non-LTS versions. With 3.1.0 release, 3.0 branch is now closed (no more patch releases planned). With 3.1.0 release, Jackson now has 3 LTS branches: 2.18, 2.21 and 3.1.

Show Me The Changes!

For the full listing of all changes, see Jackson 3.1 Release Notes. But let’s have a look at the highlights of 3.1.0 — aside from bug fixes there are plenty of improvements and even some new features.

SBOM publishing fixed

Although 3.0 build did produce CycloneDX SBOM artifacts (as per JSTEP-14), Maven publishing did not upload these as part of the version publishing, rendering this feature mostly useless (one could locally publish these but not access via Maven Central).

But 3.1.0 release published the artifacts: for jackson-databind we have (see https://repo1.maven.org/maven2/tools/jackson/core/jackson-databind/3.1.0/):

jackson-databind-3.1.0-sbom-cyclonedx.json        2026-02-23 21:13      8641
jackson-databind-3.1.0-sbom-cyclonedx.xml         2026-02-23 21:13      7755

Fix here was the same as in 2.21.0. It will be interesting to see if SBOM support proves useful or not — I have not received much (any) feedback since 2.21 release.

Most Wanted issues: 3 resolved

“Most Wanted” for Jackson means issues (mostly in jackson-databind) with at least 5 upvotes: of these 3 were resolved by 3.1.0:

  • [databind#1196]: Add support for collecting multiple deserialization failures during processing, not just first one
  • [databind#1516]: Problem with multi-argument Creator with @JsonBackReference property
  • [databind#1981]: Add method remove(JsonPointer) in ContainerNode (ArrayNode/ObjectNode)

This leaves about a dozen most-wanted issues still open. Jackson 3.2.0 being developed will fix at least one more.

Most Wanted [databind#1196]: Add support for collecting multiple deserialization failures

The oldest most-wanted issue is probably the most interesting addition. It builds on existing DeserializationProblemHandler feature: DeserializationProblemHandler allows registering a handler to try to resolve about 10 types of recoverable deserialization problems, from mismatching property names to missing type ids. In 3.1.0 anew problem handler implementation, CollectingProblemHandler, was added. It will not only provide default handling for problems (mostly just ignoring the issue, providing filler value and so on), but will also collect info on reported problems themselves. The idea is that instead of the read process immediately failing with an exception on the first issue encountered, all problems (up to configurable maximum, by default first 100) are collected to be reported as a set of issues using specific new exception type, DeferredBindingException.

Feature is used like so:

ObjectReader reader = MAPPER
   .readerFor(Person.class)
   .problemCollectingReader();
Person p;

try {
    // note: separate read() call as well
    p = reader.readValueCollectingProblems("{ \"name\": \"Bob\" }");
    // if we get here, read succeeded normally
} catch (DeferredBindingException e) {
    List<CollectedProblem> probs = e.getProblems();
    int ix = 0;
    for (CollectedProblem prob : probs) {
       System.err.printf("Issue #%d at %s: %s.\n",
          ++ix, prob.getPath(), prob.getMessage());
    }
} catch (JacksonException e) {
    // Caught non-recoverable problem (invalid JSON etc)
}

Most Wanted [databind#1516]: Problem with multi-argument Creator with @JsonBackReference property

This issue was about a bug in handling of cases like this:

// [databind#1516]
class ParentWithCreator
{
    String id, name;

    @JsonManagedReference
    ChildObject1 child;

    @JsonCreator // optional annotation in 3.x
    public ParentWithCreator(String id, String name, ChildObject1 child) {
        this.id = id;
        this.name = name;
        this.child = child;
    }
}

class ChildObject1
{
    public String id, name;

    @JsonBackReference
    public ParentWithCreator parent;

    @JsonCreator // optional annotation in 3.x
    public ChildObject1(String id, String name, ParentWithCreator parent) {
        this.id = id;
        this.name = name;
        this.parent = parent;
    }
}

which formerly failed but now works in 3.1.0.

Most Wanted [databind#1981]: Add method remove(JsonPointer) in ContainerNode

And the last “most-wanted” issue was about a simple method addition. Now following works:

// NOTE: JsonNode.asObject() added in 3.1 as well (see below)
ObjectNode doc = objectMapper.readTree(json).asObject();
JsonNode maybeRemoved = doc.remove(JsonPointer.compile("/name/first");
// maybeRemoved either node removed (if there was one), or `MissingNode` (if not)

New feature: support alternate radixes (base X) when reading/writing Numbers as (JSON) Strings

The second oldest issue resolved (filed almost 13 (!) years ago) — [jackson-databind#221] — allows reading and writing of “Stringified” numbers using different base (radix) than 10 — for example hexadecimal (base-16). “Stringied numbers” mean Java numbers written as/read from JSON Strings, not Numbers; alternate radixes are supported for integral number types (byte, short, int,long , BigInteger).

Usage is either by annotations like so:

class HexAddress {
  // Read/write as Stringified Hex
  @JsonFormat(shape=JsonFormat.Shape.STRING, radix=16)
  public int long memAddress;
}

HexAddress hex= new HexNumbers();
hex.memAddress = 64;

JsonMapper mapper = new JsonMapper();
String json = mapper.writeValueAsString(hex);
assertEquals("{\"memAddress\":\"40\"}", json);
Hex result = mapper.readValue(json, HexAddress.class);
assertEquals(64, result.memAddress);

or by configuring certain number types to default to alternate handling using standard Jackson “Config Overrides” mechanism:

ObjectMapper mapper = jsonMapperBuilder()
  .withConfigOverride(long.class,
    o->o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING).withRadix(HEX_RADIX)))
      .build();

New feature: non-buffering large reads of String values

Another old issue (in fact THE oldest one resolved by 3.1.0) from 2012 (originally [jackson-core#15] — but re-filed as [jackson-core#1228]) — requested addition of streaming alternative for JsonParser.getText() :

public class JsonParser {
  // New in 3.1!
  public long readString(Writer writer) throws JacksonException;
}

which could be used to access very long JSON String values incrementally, without buffering the whole String in memory (or at least without allocating full String).

This has been implemented for streaming JSON parser backends, as well as DataInput backed one; it is not implemented for non-blocking (async) parser (since it cannot really be implemented wrt Writer being blocking).

Default implementation used by non-JSON format backends does still full buffering — feel free to create issue requesting implementation by specific format backend if interested (implementation has to be done on format-by-format basis unfortunately, but addition is easier with JSON implementation as guideline; esp. by GenAI coding agents).

New annotations: @JsonDeserializeAs, @JsonSerializeAs

One feature added in 2.21.0 that is also included in 3.1.0 is the addition of 2 new “general” annotations (added in jackson-annotations 2.21): they were added to replace use of databind-specific older equivalents (@JsonDeserialize(as=…), @JsonSerialize(as=…)):

interface Value { ... }
class ValueImpl implements Value { ... }

class MyBean2 {
  @JsonDeserializeAs(ValueImpl.class)
  public Value value;
}
class MyOtherBean2 {
  @JsonSerializeAs(Value.class)
  public ValueImpl value;
}

See “Jackson 2.21 Released” for the full explanation.

Improvements to JsonNode functionality

In addition to [databind#1981] mentioned earlier (“Most Wanted” feature #3), a few other improvements are also included: improvements that help with streaming/functional style of working with JsonNode values:

  1. [databind#2343]: “Add JsonNode.asArray() and JsonNode.asObject() methods”. This adds simple streaming cast methods: ObjectNode doc = objectMapper.readTree(json).asObject(); which either cast or throw JsonNodeException (if it not of asserted type)
  2. [databind#3884]: “Add ObjectNode.put(JsonPointer, JsonNode) method”: doc.put(JsonPointer.compile(“/a/b/c”), doc.textNode(“value”));
  3. [databind#5558]: “Change defaulting of JsonNode.asXxx(defaultValue) /JsonNode.asXxxOpt() for NullNode“ As per title, this means that explicit null values in content will be considered same as “missing” values, and default/empty values will be returned instead of Java null (unlike in 3.0)
  4. [databind#5579]: “Add JsonNode.map()method“ String result = node.map(n -> n.asString().toUpperCase());
  5. [databind#5581]: “Add functional conversion methods JsonNode.nullAs(), JsonNode.missingAs()“ Adds 2 convenience methods for replacing NullNode s (from JSON nulls ) and MissingNodes (references to non-existing parts of JSON trees) in streams with specified or suppliedJsonNode : String result = node.nullAs(nullReplValue).missingAs(()->defaultValue()).asString();
  6. [databind#5586]: “Change IndexOutOfBoundsException that ArrayNode.set()/replace() throw to JsonNodeException”. Just what it says: avoid JDK IOOBE, throw Jackson’s own exception(s) as necessary.

Improvements to JDK Record type handling

Handling of JDK Record types was improved as well; here are the noteworthy fixes:

  • [databind#3079]: “Support ObjectMapper.updateValue() for Record classes”. [NOTE: also supports ObjectMapper.readerForUpdating()]. This was an interesting improvement to implement and relies on strict structure of Record types. Now simple usage works as expected:
MyRecord orig = new MyRecord(123, "Bob");
MyRecord updated = objectMapper.updateValue(orig, Map.of("name", "Bill"));
assertEquals(new MyRecord(123, "Bill"), updated);

// or
MyRecord updated2 = objectMapper.readerForUpdating(orig)
   .readValue("{\"id\": 456}");
assertEquals(new MyRecord(456, "Bob"), updated2);

Note: Records being immutable, a new instance will (need to be) created and returned.

  • [databind#4157]: “Add MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY to ignore getter method auto-detection for Records” This feature, when enabled, will turn off auto-detection of “classic” (get-prefixed) getters and only auto-detect (a) Record components and (b) explicitly annotated getters (@JsonProperty), like so:
record PersonRecord(String name, int age) {
  // Helper method that is NOT a record component
  public String getDisplayName() {
    return name.toUpperCase();
  }
  @JsonProperty
  public int extra() { return 42; }
}
String json = objectMapper.writeValueAsString(
  nwe PersonRecord("Bob", 37));
// json ->
//
// {"name":"bob","age":37,"extra":42}

so we will not be getting “displayName” property.

  • [databind#5223]: “Java Records missing type information with DefaultTyping.NON_FINAL (add DefaultTyping.NON_FINAL_AND_RECORDS)” Handling of Record types can be problematic with Default Typing as Record types are final — so now there is anew DefaultTypingNON_FINAL_AND_RECORDS , similar to existing NON_FINAL_AND_ENUMS.

Other improved modules

And for the rest of modules we had a few important fixes as well.

Improved modules: data formats

Aside from core modules (jackson-core, 11; jackson-databind, 80 issues resolved), most improvements were to data format modules, with total of 33 issues resolved. Here are most notable ones:

Improved dataformat: Avro

  • jackson-dataformat-avro: [avro#514] “Update to Apache Avro 1.12.1 — the latest version of Apache Avro is now used (when using Apache backend for writing (always) or reading (optional)”

Improved dataformat: CSV

  • csv#479: “ STRICT_CHECK_FOR_QUOTING does not quote value that contains newline character”
  • csv#579: “Incorrect detection of missed columns in header line if columns reordering is enabled”
  • csv#601: “Reader should allow separating plain nullValue and quoted value "nullValue"
  • csv#608: “Fix issue with UTF-8 surrogate pair decoding”
  • csv#613: “Support StreamReadConstraints.maxDocumentLength() validation for CSV module”

Improved dataformat: YAML

  • yaml#568: “YAML — ScannerException on block scalar “\n””
  • yaml#590: “Upgrade to the latest version of SnakeYAML Engine (3.0.1)”
  • yaml#596: “Port YAMLAnchorReplayingFactory from 2.x and improve it to handle nested anchors”
  • yaml#608: “Fix issue with UTF-8 surrogate pair decoding”
  • yaml#609: “Catch and rethrow yaml engine internal exception on generation”

Improved modules: Guava datatype

  • guava#211: “ GuavaMultimapDeserializer does not respect JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY

Improved modules: Blackbird

  • blackbird#334: ” BlackbirdModule does not implement java.io.Serializable

Improved modules: Mr Bean

  • mr-bean#320: “Remove byte-buddy shading from 3.1 MrBean” — now ByteBuddy is included as a regular dependency, solving possible issues with JPMS.

Next Up: Jackson 3.2 with Yet More Fixes!

So the next thing to focus on is 3.2 — and its focus will remain on clearing up the backlog: while 3.2 brough down jackson-databind open issue count from over 300 closed to 200, there’s still way to go.

So far 3.2 has about 30 fixes (see Jackson Release 3.2 page) but as with 3.1, the focus in on clearing the backlog focusing with oldest open issues — these tend to be hardest ones to solve. Still, progress now is faster than for preceding 10 years or so!

Stay tuned. :)


메타데이터
post_id
f3c962e4329d
slug
jackson-3-1-0-released-f3c962e4329d
url
https://medium.com/@cowtowncoder/jackson-3-1-0-released-f3c962e4329d
canonical_url
https://medium.com/@cowtowncoder/jackson-3-1-0-released-f3c962e4329d
author_url
https://medium.com/@cowtowncoder
status
ok
fetched_at
2026-06-25 12:15:08