Jackson 2.16-rc1 overview

@cowtowncoder
5 min readOct 31, 2023

--

Now that the first release candidate — 2.16.0-rc1 — has been released, it is time to have a look at what kind of goodies will Jackson 2.16 bring.

One more minor 2.x release

As with couple of previous 2.x releases (in fact ever since the last “big” one, 2.12), what we have here is more of an incremental release with respect to number of new features. But just like with 2.15 there are close to 100 different changes, mostly bug fixes (see 2.16 Release Notes for details).
Similarly, development time for this release was back to more traditional “twice a year” cycle (6 months release-to-release), assuming that the final 2.16.0 release happens during November.

3 initial goals, half achieved

My original thinking about goals for 2.16, at around release of 2.15, was to focus on 2 main things (as per “What Next” section of 2.15 Overview):

  1. Complete Processing Limits work that was started in 2.15 (only input side supported; missing max-doc-length and max-property-name-length limits)
  2. Get back to working on Jackson 3.0 — specifically, focus on rewriting Property Introspection in 2.x, then move focus to finalizing 3.0.0

In addition, there were initial contributions and feature requests to support some form of “Canonical JSON”: ability to produce specifically formatted JSON for purposes of comparison (“are these 2 JSON documents logically identically) and digest calculation (and possibly other use cases).

Of these, the first — Processing Limits — was more or less completed for 2.16. And the last — Canonical JSON output — had some work done (but not completed, specifically missing canonical output of floating-point numbers). This leaves the long-awaited “Property Introspection Rewrite” to be the biggest goal for 2.17.

But beyond high-level goals, here are some highlights from 95 changes included.

1. Processing Limits: 3 more limits

Jackson 2.15 introduced 3 configurable Processing Limits, to help guard against excessive resource usage (Service Protection against both malicious and accidental abuse):

  • Longest allowed number value (default: 1000 characters)
  • Longest allowed String value (default: 20 million characters)
  • Deepest nesting allowed for input documents (default: 1000 levels)

all of which are configured using the new StreamReadConstraints configuration like so:

// let's use stricter limits than defaults:
JsonFactory jsonF = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder()
.maxNumberLength(100)
.maxStringLength(1_000_000)
.maxNestingDepth(100)
.build()
).build();

With 2.16, we get 2 more limits on input side:

  • Maximum length of the input document (default: no limit)
  • Maximum length of Object property name (default: 50,000 characters)

and 1 limit for output:

  • Maximum nesting allowed for output documents (default: 1000 levels (same as input-side default))

These are configured using StreamReadConstraints and StreamWriteConstraints, respectively:

// let's use stricter limits than defaults:
JsonFactory jsonF = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder()
.maxDocumentLength(10_000_000)
.maxNameLength(1_000)
.build())
.streamWriteConstraints(StreamWriteConstraints.builder()
.maxNestingDepth(100)
.build())
.build();

This set of 6 basic processing limits constitutes the initial set of constraints planned to add originally: new ones may still be added over time, but this is the baseline we wanted to provide.

2. Configurability improvements

2.1 Configure Buffer recycling (RecyclerPools)

Possibly the most important configurability addition in 2.16 is that of ability to change how Jackson recycles underlying input and output buffers.
Work was done via [jackson-core#1089] (and related issues).

Up until now recycling has relied on an implementation where a combination of ThreadLocal (1 set of recycled buffers per Thread) and SoftReference (to allow GC as necessary) is used: no aspect is configurable, except for possibility to disable recycling altogether.
While this setup has worked reasonable well —except on platforms like Android where SoftReference does not work well — in near future introduction of Project Loom will make use of ThreadLocal less than optimal: since Threads are no longer commonly reused, Thread-bound reuse eventually becomes useless.

So 2.16 introduces a new interface— RecyclerPool — as well as multiple out-of-the-box implementations to use. Default implementation for 2.1 will remain ThreadLocal-based variant (JsonRecyclerPools.threadLocalPool()) but it is possible to reconfigure RecyclerPool to use an alternative pool:

JsonFactory jsonFactory = JsonFactory.builder()
// Let's use globally shared lock-free recycler pool
.recyclerPool(JsonRecyclerPools.sharedLockFreePool())
.build();

With 2.17 we will likely change the default RecyclerPool JsonFactory is configured with, based on experiences gained with 2.16.

2.2 Configure Caches Jackson uses

Another long-awaited configurability option is that of ability to choose different implementation than LRUMap — or at least configure cache sizes differently. By implementing new CacheProvider interface (or using differently configured DefaultCacheProvider) it is possible to change handling of 3 caches Jackson uses:

  1. Serializer cache (caches JsonSerializer instances for reuse) — default size 2000
  2. Deserializer cache (caches JsonDeserializer instances for reuse) — default size 4000
  3. JavaType cache (caches resolved types for reuse) — default size 200

Configuration is done like so:

// reduce default ser/deser cache size; increase type cache
CacheProvider cacheProvider = DefaultCacheProvider.builder()
.maxDeserializerCacheSize(200)
.maxSerializerCacheSize(200)
.maxTypeFactoryCacheSize(1000)
.build();
ObjectMapper mapper = JsonMapper.builder()
.cacheProvider(cacheProvider)
.build();

2.3 ErrorReportConfiguration

The last new configurability option is the error-reporting settings.
There are 2 aspects that can be changed:

  1. Maximum length of token (like individual JSON String value) to include during parsing exception (default: 256 characters)
  2. Maximum length of “raw content” (input for parsing, that is, input document) to include (default: 500 characters)

In both cases, entities (tokens, content) longer than limit will be truncated and only first N characters are included.

These can be configured similar to other settings, via JsonFactory (and format-specific subtypes):

ErrorReportConfiguration config = ErrorReportConfiguration.builder()
.maxErrorTokenLength(100)
.maxRawContentLength(2000)
.build();
JsonFactory f = JsonFactory.builder()
.errorReportConfiguration(config)
// !!! NOTE: without this no source content is included in exception
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.build();

One important thing to note is that “raw content” setting only has effect if StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION is enabled — one of changes in 2.16 is that this setting is disabled by default, due to security concerns wrt inclusion of input content.
See [jackson-core#1066] for more details on configuration, and [jackson-core#991] on inclusion of source in JsonLocation.

3. Enum customization with Annotations

After major improvements to Record handling in 2.15, Jackson 2.16 focused on improving handling of Enum types, especially wrt. support for standard annotations:

4. JsonNode functionality improvements

Handling of JsonNode (“Tree Model”) was also improved with:

  • Add JsonNodeFeature.WRITE_PROPERTIES_SORTED for sorting ObjectNode properties on serialization — useful for Canonical JSON output f.ex [databind#3965]
  • Add JsonNode.withObjectProperty(String), JsonNode.withArrayProperty(String) [databind#4095]
  • Change JsonNode.withObject() to work as replacement for JsonNode.with(), accepting both property name and JSON-Pointer-as-String [databind#4096]

5. Kotlin and Scala modules

jackson-module-kotlin and jackson-module-scala both had multiple significant improvements (Kotlin 8, Scala 4); see 2.16 Release Notes for details.

6. Other notable improvements

Miscellanous other improvements:

Next Steps…

Given how my initial planning for 2.16 turned out to change quite a bit by the time 2.16 development started, this too may be out-of-date by the time 2.17 development stars. :)

Having said that, the absolute number #1 priority now should be the infamous “Property Introspection Rewrite” — not only since I think that should be done in 2.x before switching to 3.0 focus, but also because Property Introspection limitations are blocking many improvements to:

  1. Kotlin module
  2. Scala module
  3. Handling of Java Record types

so doing this should unblock improvements/bug-fixes to 2 important and widely-used modules, as well as probably the Most Awesomely Practical Java Feature about, well, ever, Records.

And if 2.17 contained nothing else (… it will, of course, since we have so many contributors contributing tons of PRs :) ), that’d be worth it.

We’ll see.

Stay Tuned!

--

--

@cowtowncoder
@cowtowncoder

Written by @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