Jackson 2.16-rc1 overview
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):
- Complete Processing Limits work that was started in 2.15 (only input side supported; missing max-doc-length and max-property-name-length limits)
- 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:
- Serializer cache (caches
JsonSerializer
instances for reuse) — default size 2000 - Deserializer cache (caches
JsonDeserializer
instances for reuse) — default size 4000 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:
- Maximum length of token (like individual JSON String value) to include during parsing exception (default: 256 characters)
- 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:
- Use of mix-in annotations for Enum types is finally supported [jackson-databind#2787] (this is one of few areas where mix-in annotations did not yet work)
- Full support of
@JsonProperty
and@JsonAlias
for Enum types: [databind#4036] , [databind#4037], [databind#4039] and [databind#4040].
4. JsonNode functionality improvements
Handling of JsonNode
(“Tree Model”) was also improved with:
- Add
JsonNodeFeature.WRITE_PROPERTIES_SORTED
for sortingObjectNode
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 forJsonNode.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:
- [jackson-core#1042]: Allow configuring spaces before and/or after the colon in
DefaultPrettyPrinter
- [databind#3647]:
@JsonIgnoreProperties
not working with@JsonValue
- [databind#4061]: Add
JsonTypeInfo.Id.SIMPLE_NAME
which defaults type id toClass.getSimpleName()
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:
- Kotlin module
- Scala module
- 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!