Jackson 2.15 release: Overview

@cowtowncoder
9 min readMay 6, 2023

--

Now that Jackson 2.15 has been released (April 23, 2023) it is time for a quick overview what is included.

Another “minor minor” release

As with 2.14 what we have can be considered a “small” minor release, with few foundational changes. However the number of changes — almost 100 as per 2.15 Release Notes! — is higher than with 2.14, mostly due to even wider participation of the development community (more active contributors as time goes by which is GREAT).

Conversely the time for development was less (6 months, instead of 9 months). A significant reason for more compressed development cycle was the desire to get the Processing Limit changes (more on this below) out sooner rather than later. We also think that it is also good, in general, to have more frequent releases over bigger bundles that take longer.
Perhaps this trend can continue for 2.16 release as well.

Be that as it may, here’s an overview of changes by categories. As usual, the full 2.15 Release Notes give the complete picture. I have and will also dig into some of changes deeper in separate posts.

1. Faster floating-point number reading/writing performance

One of performance-sensitive areas with textual formats is that of reading and writing of non-integer numbers (aka Decimal or Floating-Point numbers): numbers with a fraction part like 1.25 (compared to integers like 100 and -25). These are more expensive to process than integers or String values because their textual format is in base-10 notation, but processing internally is usually done using compact base-2 encoding: converting between the two is expensive. Especially compared to binary formats which typically encode and transfer such numbers using “native” base-2 encoding ( standard IEEE-754 float, double) and do not incur similar conversion overhead on reading or writing.

Some improvements to reading and writing performance were already introduced in Jackson 2.14, but 2.15 updated versions of libraries used and ensured usage by JSON module, as well as some of other textual backend formats.

I wrote more about this earlier in these posts so read on if interested in details:

note: if you would like to enable faster reads, writes, they are enabled like so

JsonFactory fastFactory =
JsonFactory.builder()
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
.enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)
.build();
ObjectMapper mapper = JsonMapper.builder(fastFactory)
.build();

as we have not (yet?) enabled them by default due to caution — in theory it is possible conversion results could differ from that of JDK default handling. Note that we have not received any bug reports to suggest this actually occurs (“abundance of caution”).

2. Processing Limits

One of biggest drivers for 2.15 were requests to add processing limits — something planned since 2.13. This refers to ability to limit maximum dimensions of various JSON values (and where applicable, other formats’ too). The reason for doing this is Service Protection: preventing likelihood that individual request (or a small set of concurrent requests) can consume so many resources that it prevents service from processing other concurrent requests. This may occur accidentally (client-side implementation defects) or on purpose by bad actors (Denial-of-Service (DoS) attacks).

I will write a bit more about this in a separate post, but here’s a brief summary in bullet points:

  • Jackson 2.15.0 adds 3 types of initial limits: maximum Number value length (default: 1000 characters); maximum String value length (5 million characters); maximum Document Nesting (1000 nested Arrays and/or Objects)
  • If input document has constructs with bigger dimensions, StreamConstraintsException (subtype of JsonProcessingException) will be thrown
  • Limits may be changed by changing StreamReadConstraints assigned to JsonFactory. ObjectMapper uses JsonFactory to create JsonParser instances and parser instances enforce limits.

Of these limits, there has been some initial feedback that the default Maximum String Value Length may be too strict for some use cases — if so, please let us know via jackson-core Issue Tracker or mailing lists (similarly wrt other limits). Defaults may be tuned (limit increased) for 2.15.1 patch if there is demand — currently it seems there will be a change to 10 or maybe 20 megs instead of 5.

**EDIT** 2023/05/19: Jackson 2.15.1 was released with increased limit, 20 megs as suggested above.

3. Improved support for Records wrt. Annotations

Although Java Record type — introduced as preview feature in JDK 14 — has been supported by Jackson for a while (since Jackson 2.12, in fact) there have been some rough edges.
Specifically, many Jackson Annotations have not either worked at all for Records, or have required awkward placement (or duplication) of Annotations.

Jackson 2.15 improves this via following fixes:

Note: we have received a few reports that there are new issues (regressions) wrt Visibility changes, especially regading Fields (see #3736 above). We hope to fix these in patch release (2.15.1) where possible.

4. Improved support for Enums

Jackson has supported Enums for a long time (since before 1.0). But due to differences between Enums and POJOs/Beans, some functionality available for latter has not been available for Enums. 2.15 addresses some of these differences, further improving Enum reading/writing support.

Notable fixes include:

  • jackson-annotations#211/ jackson-databind#3637 Add JsonFormat.Features: READ_UNKNOWN_ENUM_VALUES_AS_NULL, READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE
  • jackson-annotations#221: Add JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS
  • jackson-databind#2536: Add EnumFeature.READ_ENUM_KEYS_USING_INDEX to work with existing “WRITE_ENUM_KEYS_USING_INDEX”
  • jackson-databind#2667: Add @EnumNaming, EnumNamingStrategy to allow use of naming strategies for Enums — similar to PropertyNamingStrategy for POJO naming
  • jackson-databind#3053: Allow serializing enums to lowercase (EnumFeature.WRITE_ENUMS_TO_LOWERCASE)
  • jackson-databind#3566: Cannot use both JsonCreator.Mode.DELEGATING and JsonCreator.Mode.PROPERTIES static creator factory methods for Enums
  • jackson-databind#3638: Case-insensitive and number-based enum deserialization are (unnecessarily) mutually exclusive

These can be grouped further as:

  • Significant new feature (incomplete), @EnumNaming
  • New configuration mechanism EnumFeature with one new option
  • Ability to apply more of existing enum-specific global features on specific Enum-valued properties using @JsonFormat.Feature mechanism

These features will still be further developed in 2.16 and beyond, based on feedback, so let us know of improvement ideas, usage experiences.

5. New modules

5.1 JAXB vs Jakarta: JSON Schema module

The main source for new Module variants lately (since 2.13 release) has been on-going shift in the Java world from “old” Javax APIs to “new” Jakarta APIs: process that has been as painful as it’s been slow.
For an overview see f.ex “Java EE vs J2EE vs Jakarta EE”.

Jackson core components — annotations, streaming (jackson-core), databind — do NOT have any dependencies to JAXB or Jakarta APIs. But some modules do: for example, JAXB annotation support module obviously does, as does JAX-RS providers package.

Jackson’s approach has been to provide separate modules, instead of attempts to use other seemingly simpler tactics (such as automatic conversions, or Maven artifact qualifiers) — mostly since keeping transitive dependencies aligned throughout transitive dependency chain is VERY difficult otherwise. This means that changes to adopt new Jakarta APIs for Jackson are explicit: using code needs to use specific Module dependency (JAXB vs Jakarta).

Now: Jackson 2.13 introduced Jakarta counterparts to most modules — afore-mentioned JAXB-annotations (jackson-module-jakarta-xmlbind-annotations), JAX-RS Providers (jackson-jakarta-rs-json-provider), JSON-P datatype (jackson-datatype-jakarta-jsonp) and Hibernate 5 (jackson-datatype-hibernate5-jakarta)

But 2.15 fills one remaining gap: (deprecated) Jackson JSON Schema module now has two variants as well: old jackson-module-jsonSchema for JAXB APIs and jackson-module-jsonSchema-jakarta for Jakarta APIs.

5.2 Hibernate 6

Beyond JAXB/Jakarta changes there is one more Module extension:

was added for improved databinding with Hibernate 6: this version is based on Jakarta APIs only (whereas Hibernate 5 versions exist for both APIs).

6. Modules with significant improvements

First and foremost, Jackson Kotlin module was improved a lot:

  • 12 fixes/improvements in 2.15.0!
  • Issue tracker cleaned up to better reflect the current state of work
  • Got a new Active Maintainer (mr “wrong wrong”, @k163377 ) — which explains 2 above points :)

Aside from that “Most Improved” modules list contains at least following:

  • Scala module: actively clearing up Issue Tracker, as well as a few fixes
  • XML module: 7 fixes/improvements; especially for handling of POJOs with empty constructors (via @JsonCreator)
  • YAML module: 4 fixes/improvements, as well as upgrade to SnakeYAML dependency to quiet down pesky Security Scanners wrt CVE-2022–1471 (NOTE! no version of Jackson YAML module was ever vulnerable, but Scanners often still report it as potential problem)
  • Guava datatype module: improved support for Multimaps, Ranges and immutable Collection types (all due to contributions by a new very active contributor, @JooHyukKim!)

7. Other Ergonomic Improvements

7.1 Infer “Delegating” Creator from @JsonValue

One commonly encountered challenge (as explained in issue databind#3654) with use of @JsonCreator to mark constructor for use in deserialization is the ambiguity in acse of 1-argument constructors.
POJO like:

public class Value {
@JsonCreator
public Value(String text) { ... }
}

could be expected to match one of following JSON values:

"abc"

{ "text" : "abc" }

because it could either be matched as-is (first case, so-called “Delegating” mode) OR map to one of properties of Object (so-called “Properties-based”).
Problem does not occur if more than 1 argument is defined for constructor.

Before 2.15 there is a somewhat complex heuristics used which will try to see if POJO in question might have properties that match name of the one argument (if so, choose Properties-based style). It often guesses intent incorrectly, requiring use of explicit mode:

public class Value {
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public Value(String text) { ... }
}

What 2.15 adds is to consider this common case:

public class Value {
private String text;

@JsonCreator // optional if only one public constructor exists
public Value(String text) { this.text = text; }

@JsonValue
public String getText() { return text; }
}

and upon seeing use of @JsonValue will consider this to be “Delegating” case even if there is indication of possible property (in this case existence of getText() would imply existence of property named “text” and would have defaulted to “Properties-based” before 2.15)).
Use of @JsonCreator annotation itself should now be optional in this case as well.

7.2 More Convenient Iteration over ObjectNode properties

One of common tasks when using “Tree Model” -based processing — that is, use of JsonNode as binding target, instead of POJOs — is to iterate over Properties of ObjectNode which represents JSON Object values.
There is a method for iteration already — ObjectNode.fields() — but unfortunately it returns Iterator<Map.Entry<String, JsonNode>>and Iterators are quite cumbersome to use.

What 2.15 adds (see databind#3809) is new method, JsonNode.properties(), which instead returns Set<Map<String, JsonNode>> allowing use of Streaming like so:

JsonNode doc = objectMapper.readTree(source);
String desc = doc.properties().stream()
.map(e -> e.getKey() + "/" + e.getValue())
.collect(Collectors.joining(","));

8. What Next? :)

The immediate next step is to go through all the issues reported against 2.15 — and there are probably more of these than for 2.14.0, it seems. It would be good to know why our Pre-Release candidates (2.15.0-rc1, rc2, rc3) failed to catch as many issues as previous RCs. Perhaps this was due to more ambitious nature of some of fixes, contributed by more recent(ly active) contributors. But be that as it may, first things first: get 2.15.1 out with as many fixes to regressions as possible.

Along with that, I really hope we can keep momentum with GREAT community participation, increased contributions by Active “Repeat Contributors” (including but not limited to our new module maintainers).
This allows Jackson project to scale better: my time will be mostly spent coordinating, reviewing PRs, offering feedback. While it is sometimes frustrating not to be able to develop as much myself (due to most time being spent working with other), I think that is ultimately more effective use of my time— I get to work as a Multiplier.

But. After immediate fix-it patch (2.15.1) there are 2 paths to take:

  1. Focus on 2.16 with things like extended Processing Limits, and — I hope, finally! — rewritten Property Introspection logic (to further help with Record types, Kotlin/Scala types). And whatever other things User/Dev community feels are most important.
  2. Make Jackson 3.0 release reality: 3.0 branch (master) has been ready for years now (literally), and has important improvements to provide (read “Jackson 3.0 Vision” I wrote 2 years ago, after Jackson 2.12 release). But all this work is of little use without releasing a version. I have been waiting to “just get that Property Introspection done in 2.x, then switch gears” but… it hasn’t still happened.

I am starting to feel that unless I start focusing on (2) more, it will never happen. We’ll see: either I need to really, really focus on (1) with explicit goal of Property Introspection rewrite; or consider rewrite to be 3.0-only — but it would be so good to start with clean slate with 3.0 (that is, 3.0.0 having rewritten introspection already; but one that is somewhat compatible with latest 2.x releases).

We’ll see. As usual, please send me all feedback!

--

--

@cowtowncoder

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox