Skip to content

Jackson Release 3.3

Tatu Saloranta edited this page Nov 13, 2026 · 279 revisions

Jackson Version 3.3 was released on November 7, 2026. It contains all fixes up to LTS release 3.1.9 (in addition to what is listed here)

This wiki page gives a list of links to all changes (with brief descriptions) that are included.

Status

Version is under development (under branch 3.x).

It is not designated as Long-Term-Support (LTS) since 3.1 is an LTS.

Patches

  • 3.3.1 (Not yet released)

Documentation

Articles, Blog posts

New Modules

jackson-datatype-fastutil

TO BE WRITTEN, see https://github.com/FasterXML/jackson-datatypes-collections

Changes, compatibility

Android

SDK 34 for all components (since Jackson 3.2; up from 3.1). Verified by "Animal Sniffer" plug-in.

JDK

Same as Jackson 3.2; Java 17, except:

  • JDK 25 for [jackson-module-blackbird]

Kotlin

Minimum supported Kotlin has been upgraded to 2.2.

Compatibility:modules

Blackbird

Now requires JDK 25 (unlike JDK in 3.2 and before), after full rewrite via modules-base#359.

Changes, behavior

None.


Major focus area(s)

Performance

TONS of performance improvements, f.ex:

[TO BE WRITTEN]

Most wanted Issues resolved

Issues with at least 5 thumbs-ups that were resolved in this version:


Full Change list

Changes, core

No separate 3.x versions released, uses jackson-annotations 2.23.

  • #1637: Add JsonPointer.startsWith() method
  • #1646: Non-blocking ByteBuffer parser releaseBuffered() can write already-consumed bytes
  • #1652: Use faster, no-allocation DoubleToDecimal / FloatToDecimal
  • #1654: Stale symbol table child can replace a newer root table
  • #1664: UTF-8 byte-backed parsers report raw byte instead of decoded character for invalid root-value separator
  • #1674: Encode base64 into exactly-sized char[] instead of StringBuilder
  • #1675: Allocate ByteArrayBuilder past-block list lazily
  • #1680: Avoid per-character escape-table load in _writeStringSegment ASCII loop
  • #1685: Revisit BigDecimalParser's 500-character FastDoubleParser threshold (lower to 200)
  • #1686: Synchronous decimal number parsing buffers oversized values before maxNumberLength validation
  • #1687: Add StripedArrayPool and virtual-thread-aware HybridPool recycler pools
  • #1688: Add JsonParser.nextNameMatchAndToken(): fused name match plus value-token advance
  • #1691: Validate name length in UTF8DataInputJsonParser before quad buffer growth
  • #1697: Add ByteArrayUtil for codecs to use
  • #1704: Add NumberOutput.outputDouble/outputFloat(char[]), use in WriterBasedJsonGenerator
  • #1706: Write number text directly as ASCII bytes in UTF8JsonGenerator
  • #1715: Add StreamReadConstraints.getMaxBigIntegerScale()
  • #1727: Fix UTF-8 surrogate validation in String skip path
  • #1731: FilteringGeneratorDelegate.writeUTF8String() and writeRawUTF8String() filtered via includeRawValue() instead of includeString()
  • #1738: Add GuardedOutputStream (non-flushing, non-closing OutputStream wrapper) for textual format backends
  • #1752: UTF8DataInputJsonParser sould preserve I/O failures during invalid token reporting
  • #1753: Reject 4-byte UTF-8 sequences exceeding U+10FFFF
  • #1760: Extend 4-byte UTF-8 range validation to remaining parser paths
  • #1766: ReaderBasedJsonParser undercounts number length when fraction or exponent is absent
  • #1768: MergedStream edge cases: negative skip(), empty pushback range
  • #1771: ByteArrayBuilder.write(byte[], int, int) does not validate off/len
  • #1778: Use local pointer in UTF8StreamJsonParser long-name loop
  • #1782: Overlong 2- and 3-byte UTF-8 sequences in String values and comments are decoded instead of rejected
  • #1784: UTF-8 parsers may return wrong cached property name for names containing 0xFF bytes
  • #1787: CharTypes#charToHex handles non-ASCII incorrectly
  • #1792: Validate maxStringLength for string values in getString(Writer)
  • #1794: maxStringLength not enforced by getBinaryValue() when decoding Base64 directly from input
  • #1795: UTF8DataInputJsonParser accepts any control character as white space between tokens
  • #1798: BinaryNameMatcher does not pad partial quads: binary parsers match NUL-containing names to known properties
  • #1800: Don't read past end of byte[] slice for UTF-16 input in ByteSourceJsonBootstrapper
  • #1801: Reject surrogate code points in UTF32Reader
  • #1802: Pad BufferRecycler slots to avoid false sharing
  • #1803: Validate async decimal number lengths before buffer expansion
  • #1813: getDoubleValue() returns 0.0 after getNumberType(), getFloatValue() for floating-point value
  • #1814: getBigIntegerValue() fails after getNumberType(), getDoubleValue()/getFloatValue() for BigInteger
  • #1815: Stale BigDecimal from previous token returned by getBigIntegerValue() after getNumberType()
  • #1127: @JsonTypeInfo with EXTERNAL_PROPERTY doesn't handle arrays of polymorphic types
  • #1803: TreeTraversingParser should allow passing of parent context
  • #3119: Map Key interface not serialized with both type serializer or keyUsing
  • #3182: Support value deduplication for enumeration like values: add StringCanonicalizingConverter
  • #3947: Multiple suitable annotated Creator factory methods to be used as the Key deserializer when using record as key in Map
  • #4762: @JsonValue ignores annotations on annotated field (particularly @JsonInclude)
  • #5380: @JsonValue ignores propertyInclusion rules for config override
  • #6068: 3.x ObjectMapper.convertValue() stop throwing java.lang.IllegalArgumentException
  • #6091: Current token incorrectly reported when using @JsonTypeInfo
  • #6113: Fix DOMSerializer to support polymorphic serialization
  • #6114: Fix QName object deserialization to reject non-String namespaceURI/prefix
  • #6120: Use per-node-type Type Ids for DOM values, reconstruct via DOM factory instead of always parsing a Document
  • #6128: Fail explicitly on POJO that has both @JsonFormat(shape=ARRAY) and @JsonFilter
  • #6130: Allow limiting maximum length of JDK regex Pattern at deser time
  • #6136: @JsonFilter with serializeAllExcept() does not filter @JsonAnyGetter entries
  • #6142: SerializerCacheread-only lookup is not invalidated when replacing typed serializers
  • #6151: Add DateTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS
  • #6168: Cache raw parameter types in AnnotatedConstructor
  • #6170: Allow skipping null values for properties handled by @JsonAnySetter
  • #6173: Allow BeanSerializerModifier to override property serializers
  • #6175: QName object form is skipped when polymorphic type id is written As.PROPERTY
  • #6178: JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES does not work for java.time.Month
  • #6187: Make InvokerHolder access in AnnotatedConstructor lazy, to reduce unnecessary eager processing
  • #6188: ThrowableDeserializer does not resolve standard Throwable property names against PropertyNamingStrategy
  • #6190: Explicit @JsonView on a standard Throwable property is ignored by ThrowableDeserializer
  • #6193: Use JsonParser.nextNameMatchAndToken() in the vanilla BeanDeserializer loops
  • #6195: Close managed parser if readValues() initialization fails
  • #6196: Close generator if initialization fails before any content is written in ObjectMapper, ObjectWriter
  • #6197: Fix ClassCastException for AutoCloseable values in ObjectMapper.writeValue(JsonGenerator, ...)
  • #6198: Make InvokerHolder access in AnnotatedMethod lazy, to reduce unnecessary eager processing
  • #6210: @JsonAlias ignored in enum if @JsonValue is present
  • #6219: Vanilla BeanDeserializer fast path never used with default settings in 3.x (due to MapperFeature.DEFAULT_VIEW_INCLUSION default change)
  • #6216: Add TreeTraversingParser constructors taking explicit StreamReadConstraints
  • #6220: Build a property's type deserializer for the type @JsonDeserialize(as/keyAs/contentAs) refines it to
  • #6229: MapperFeature.DETECT_PARAMETER_NAMES=false silently disables @ConstructorProperties-based Creator detection too
  • #6233: Serialize Month as Enum name with @JsonFormat(shape = STRING)
  • #6239: Explicit JsonFormat.Shape.NUMBER_INT/NUMBER_FLOAT not honored for nanosecond handling of java.time values
  • #6243: Filter ignored Creator properties at build time instead of per-loop read-time checks
  • #6255: @JsonIgnoreProperties/@JsonIncludeProperties not honored for ObjectNode/JsonNode @JsonAnyGetter entries
  • #6257: MapDeserializer: @JsonIgnoreProperties/@JsonIncludeProperties checked after key deserialization, so ignored non-convertible key names still fail
  • #6275: Per-type @JsonInclude(NON_DEFAULT) no longer writes a null over a non-null default, so the null can't round-trip
  • #6295: @JsonInclude(NON_DEFAULT) omits a changed array property when the default array contains null
  • #6296: Duplicate properties merged under the wrong key in non-vanilla UntypedObjectDeserializer
  • #6300: NPE when a DeserializationProblemHandler returns null for byte[] (single value unwrapped) and char[] elements
  • #6305: InetSocketAddress deserialization ignores text after the closing bracket of an IPv6 address
  • #6306: NPE serializing @JsonIdentityReference(alwaysAsId = true) to an object whose id is null
  • #6309: NPE serializing an Enum whose toString() returns null
  • #6315: Map key deserializers should apply same type checks as bean deserializers
  • #6317: Global NON_DEFAULT with USE_REAL_INCLUDE_NON_DEFAULT now writes explicit null` over a non-null default (like per-type); previously nulls were suppressed
  • #6319: Global NON_DEFAULT with USE_REAL_INCLUDE_NON_DEFAULT skips "empty" check when default bean cannot be created
  • #6320: Locale Map keys are still serialized using Locale.toString() instead of Locale.toLanguageTag()
  • #6324: ArrayIndexOutOfBoundsException in BeanPropertyMap.withoutProperties()with property-level@JsonIdentityInfo+@JsonIgnorePropertieswhen the value type has@JsonAlias`
  • #6325: NullPointerException when a @JsonCreator returns null and the input has an Object Id forward reference
  • #6329: Add JDK-bundled RowSet gadget siblings to deserialization deny-list
  • #6332: @JsonUnwrapped drops/rejects the type id property of a polymorphic unwrapped value
  • #6338: readerForUpdating() ignores @JsonSetter(nulls = Nulls.SKIP) for creator properties: existing value is overwritten with null
  • #6339: Enum aliases from an appended AnnotationIntrospector are discarded: JacksonAnnotationIntrospector.findEnumAliases() overwrites every slot
  • #6342: @JacksonStdImpl on BooleanSerializer.AsNumber (3.1.6) makes Blackbird write primitive boolean getters as true/false instead of 1/0
  • #6350: allowSubTypesWithExplicitDeserializer() denies java.sql.Date and java.sql.Timestamp: hasExplicitDeserializerFor() returns false for them
  • #6352: BeanPropertyMap.withProperty() modifies shared map when Object Id property name matches existing property (property-level @JsonIdentityInfo)
  • #6357: Property-based creator loop (and vanilla loop after unknown property) silently stop on unexpected token within Object, leaving parser mid-Object
  • #6363: @JsonFormat(shape = ARRAY) Builder loses property values when not using vanilla processing (e.g. with active View): deserializeSetAndReturn() result ignored
  • #6364: Report property name as found in input (alias) in deserialization error paths
  • #6365: JsonMapper.builder().filterProvider(...).build() throws NPE when a SimpleFilterProvider has a null filter
  • #6366: NullPointerException ("Null 'propertyType' passed") deserializing raw Map.Entry with a Shape.POJO config override
  • #6372: Preserve immutable builder updates for unwrapped properties
  • #6374: @JsonFormat(shape = ARRAY) Builder: structured first extra array element not skipped correctly, leaves parser misaligned
  • #6375: Wrap non-Jackson exceptions from BeanDeserializerBase.handleUnknownProperty()
  • #6385: Record update with readerForUpdating() fails for non-public Record: accessor not made accessible
  • #6394: @JsonFormat(shape = ARRAY) deserialization hangs when input ends before END_ARRAY while skipping extra values
  • #6398: @JsonFormat(shape = ARRAY) builder: null from DeserializationProblemHandler causes NPE
  • #6399: NullSerializer should not be annotated with @JacksonStdImpl
  • #6401: Raw IllegalArgumentException escapes polymorphic type-id resolution
  • #6410: Object Ids should NOT be affected by JSON Views or @JsonFilter

Changes, data formats

Avro

  • #762: Use varhandle for float/double reads in Avro parser
  • #775: Update apache Avro dep to 1.12.2
  • #777: Properties following an @AvroEncoded value are silently dropped
  • #812: Avro: deserializing as Object fails with InvalidTypeIdException for unresolvable type id (3.x regression from 2.x)
  • #862: Invalid UTF-8 sequences (overlong, surrogates, > U+10FFFF, bad continuation bytes) not rejected in Avro string values
  • #896: Reject negative byte size of skipped Avro Array/Map block
  • #901: Read string values and Map keys in chunks in Apache Avro decoder

CBOR

  • #624: Use more efficient (in JDK 17) String construction wrt "Compact Strings"
  • #752: Use varhandle for array access in CBORGenerator
  • #758: Use SWAR in CBORParser
  • #823: maxStringLength not consistently checked for chunked (indefinite-length) CBOR text values
  • #860: Invalid UTF-8 sequences (overlong, surrogates, > U+10FFFF) not rejected in CBOR names, text values
  • #876: nextNameMatch() mixes up unknown property names differing only by NUL bytes (unpadded symbol-table lookup)
  • #882: nextNameMatch() does not pop stringref nesting on END_OBJECT, resolves later stringrefs against wrong namespace
  • #870: Reject 0xFF lead byte in last quad of property names
  • #888: nextFieldName(SerializableString) matches against JSON-escaped name bytes (asQuotedUTF8())

CSV

  • #699: Add ability to modify columns in CsvSchema.Builder by name
  • #718: Fix AUTO_CLOSE_SOURCE overrides and recycled-buffer leaks on read paths
  • #719: Fix output lost when writing to OutputStream with auto-close and flush both disabled
  • #721: Write double/float directly into output buffer in CsvEncoder when fast writer enabled
  • #724: Single-pass String value writing in CsvEncoder; native char[] path; primitive array appends
  • #727: Avoid char[] copy of value text when parsing integer numbers in CsvDecoder
  • #763: Overlong UTF-8 sequences not rejected in CSV

Ion

  • #780: IonFactory leaks stream when parser/generator construction fails for File/Path

Properties

  • #718: Fix AUTO_CLOSE_SOURCE overrides and recycled-buffer leaks on read paths
  • #720: TOML, Java Properties double-close File/Path source on construction failure
  • #722: Use NumberOutput for number writes in Properties, TOML and YAML generators
  • #734: Fix output lost when writing to OutputStream with auto-close and flush both disabled
  • #748: Honor stream-write features changed on generator after creation (follow-up to #734, #735)
  • #751: Properties WriterBackedGenerator: no flush after close, expose caller's stream as output target, don't mask close failures

Protobuf

  • #398: Add ProtobufReadFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE for unknown ids of "non-standard" enums
  • #763: Use varhandles to to improve perf in Protobuf Parser and Generator
  • #863: Invalid UTF-8 sequences (overlong, surrogates, > U+10FFFF, bad continuation bytes) not rejected in Protobuf string values
  • #874: nextName() / nextNameMatch() do not skip an unread string value, decode its bytes as the next tag
  • #879: nextName() / nextNameMatch() fast paths skip per-token state reset done by nextToken() (stale number, wrong token location)
  • #881: Known field after a skipped unknown field gets wrong wire type / state / location, differently per nextToken() / nextName*()

Smile

  • #750: SmileGenerator: avoid String allocation in SmileGenerator.writeString(char[],int,int)
  • #757: Use SWAR in SmileParser
  • #759: Use more efficient String construction wrt "Compact Strings" for Smile "long" text values
  • #767: Optimize async short ASCII string decoding
  • #774: Use SWAR + VarHandle for 7-bit binary Smile encode/decode
  • #781: SmileGenerator.writeNumber(String) validates number length against default StreamReadConstraints instead of configured ones
  • #861: Invalid UTF-8 sequences (overlong, surrogates, > U+10FFFF, bad continuation bytes) not rejected in Smile string values
  • #869: Reject 0xFF lead byte in last quad of property names
  • #873: nextNameMatch() misreads short non-ASCII property names (input not advanced on symbol-table hit, wrong quad count)
  • #885: Cheap validation of short Smile ASCII property names, to prevent symbol table by-pass
  • #888: nextFieldName(SerializableString) matches against JSON-escaped name bytes (asQuotedUTF8())
  • #889: Speed up UTF-8 validation in Smile: check 3- and 4-byte sequences with a second-byte range lookup keyed on lead byte
  • #895: Enforce maxNestingDepth in non-blocking Smile parser

TOML

  • #663: TOML parser: int64 max value parsed as truncated long
  • #713: Misc perf fixes (TOML)
  • #718: Fix AUTO_CLOSE_SOURCE overrides and recycled-buffer leaks on read paths
  • #720: TOML, Java Properties double-close File/Path source on construction failure
  • #722: Use NumberOutput for number writes in Properties, TOML and YAML generators
  • #725: TOML: lookup table for ASCII string categorization; cheaper writeRaw() variants
  • #735: Fix output lost when writing to OutputStream with auto-close and flush both disabled
  • #739: TreeTraversingParser created by TomlFactory does not use factory's StreamReadConstraints
  • #747: TomlGenerator: centralize value termination (writeValueEnd()) to avoid missing/duplicate line feeds
  • #748: Honor stream-write features changed on generator after creation (follow-up to #734, #735)
  • #750: TomlGenerator: no flush after close, expose caller's stream as output target, don't mask close failures
  • #755: TomlParser: use a single StreamReadConstraints source
  • #758: TomlGenerator: invalid TOML output for root-level values
  • #765: Wrong byte position (byte #-1) in invalid UTF-8 error messages
  • #781: TomlGenerator.writeName(): rejected key leaves generator stuck
  • #873: Fix handling of @JsonApplyView
  • #878: Re-apply entity/DTD hardening after JDK deserialization of XmlFactory
  • #879: Use Locale.ROOT for case folding in CaseInsensitiveNameSet
  • #882: Translate base64 name-decode errors to StreamReadException
  • #883: Translate XMLStreamReader creation errors to StreamReadException
  • #886: Escape names already starting with prefix in Base64NameProcessor
  • #887: Apply XmlNameProcessor to unwrapped ObjectNode root element name
  • #888: Honor xsi:nil in any attribute position and xs:boolean form
  • #890: Update token on 19-digit long coercion in isExpectedNumberIntToken
  • #893: Decode element name before matching virtual wrapper in _initStartElement
  • #895: Fix leading digit in AlwaysOnBase64NameProcessor encoded names
  • #899: Return null from nextStringValue() at end of input
  • #901: Include ENTITY_REFERENCE replacement text in element values (instead of silently dropping it)
  • #903: Split embedded ]]> across CDATA sections when serializing
  • #905: Guard ToXmlGenerator cast in serializeWithType()/_serializeObjectId() for convertValue()
  • #907: Recompute XML metadata for filtered props in XmlBeanSerializerBase
  • #909: Clear forced xsi:type attribute state in ToXmlGenerator.writeName
  • #913: Keep Builder.defaultUseWrapper() from modifying shared introspector
  • #918: Apply XmlNameProcessor consistently to all written element/attribute names
  • #921: Escape colon in XML names written by escaping XmlNameProcessors

YAML

  • #713: Misc perf fixes (YAML)
  • #718: Fix AUTO_CLOSE_SOURCE overrides and recycled-buffer leaks on read paths
  • #722: Use NumberOutput for number writes in Properties, TOML and YAML generators
  • #749: Fix YAMLGenerator flushing/closing of OutputStream target (follow-up to #735)
  • #752: YAMLGenerator: close target even if writing out buffered content fails with an unchecked exception (follow-up to #749)
  • #764: Overlong UTF-8 sequences not rejected in YAML
  • #773: Fix a regression: keep END_OBJECT current for empty YAML documents

Changes, data types

  • #240: Most types fail to round-trip with default typing enabled; primitive maps silently lose data
  • #251: Pair / Triple deserialization fails for raw types and implementation classes (default typing); Triplet read as TripleImpl
  • #264: Avoid allocating a char[] per entry when serializing char-valued primitive maps
  • #270: Use NumberInput to parse numeric map keys, as jackson-databind does
  • #38: Support for fastutil collections
  • #2: Better Guava Multiset serialization / deserialization
  • #232: Invalid stringified Range and incomparable sorted elements throw raw runtime exceptions
  • #233: Nulls.SKIP on immutable collection contents drops nulls returned by value deserializer
  • #241: RangeSetSerializer does not implement serializeWithType(): RangeSet fails to serialize with default typing
  • #242: Guava ImmutableIntArray / ImmutableLongArray / ImmutableDoubleArray serializers do not implement serializeWithType()
  • #243: Guava HashCode cannot be deserialized with default typing: type id is non-public subclass
  • #244: Guava Multimap / RangeMap / Cache with Object values serialize each value as {} even when root type is given
  • #245: ImmutableSortedSet / ImmutableSortedMultiset cannot be deserialized as Object with default typing
  • #253: Avoid redundant copy when deserializing Guava Multimap already of requested type
  • #256: Avoid boxing every element when deserializing Ints.asList (etc) primitive lists
  • #273: Report nulls rejected by Multimaps and Tables as input mismatch; apply content null handling to single-value Multimaps
  • #274: Report current token to problem handler when value deserializer returns null for structured value
  • #204: Add Hibernate7Module.Feature.REPLACE_CYCLES_WITH_NULL to handle bi-directional graphs
  • #205: Update Hibernate version to 7.4.1.Final
  • #208: Avoid creating unnecessary temp session
  • #258: Hppc BitSet serialized up to allocated capacity (size()), not highest set bit
  • #260: Specialized IntIndexedContainer serializer never used (checks JavaType.getClass() instead of raw class)
  • #262: Avoid copying ByteArrayList / CharArrayList contents on serialization
  • #91: Fail cleanly on non-Object input instead of throwing NullPointerException
  • #191: Reject malformed zone id suffix in DateTimeDeserializer
  • #192: Resolve LocalTime timestamps with the configured time zone
  • #193: Reject partial LocalDateTime array instead of returning null
  • #194: Fix timestamp time zone resolution (explicit zone equal to JVM default; ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
  • #195: Invalid String values for LocalDate/LocalDateTime/LocalTime/DateTime fail with raw Joda IllegalArgumentException
  • #197: DateTimeZoneDeserializer leaks raw NumberFormatException for out-of-int-range timestamp String
  • #199: Reject timestamp that overflows zone offset in Local* deserializers
  • #200: Reject overflowing period in IntervalDeserializer instead of leaking ArithmeticException
  • #202: Use context time zone (not JVM default) for values without offset
  • #203: Reject DateTime/Instant text that overflows the Instant range
  • #204: Reject out-of-range year for LocalDate/LocalDateTime String, Array and key values
  • #90: Fail on non-Object input for JSONObject instead of quietly returning empty Object
  • #96: Support embedded binary in Arrays, as already done for Objects
  • #99: Port JsonPatch START_ARRAY check from jsr-353 module
  • #93: Fix MonetaMoneyModule.getModuleName() returning "JavaxMoneyModule"
  • #229: Add deserializers for PCollection TreePSet, TreePMap, OrderedPMap and AmortizedPQueue types
  • #237: PStack / ConsPStack element order is reversed on round-trip

Changes, Other modules

Afterburner

  • #368: Report property name as found in input in deserialization error paths

Blackbird

  • #359: Blackbird rewrite: ClassFile generation of unrolled codec class, optimize record / builder / field
  • #363: Blackbird ignores @JsonSetter(nulls = Nulls.SKIP) for properties of beans with a properties-based creator
  • #368: Report property name as found in input in deserialization error paths

Changes, Providers

  • #69: ProviderBase.readFrom() fails to advance past START_ARRAY for MappingIterator with JSON array input
  • #77: Minor jetty, jersey test updates
  • #78: Upgrade Jersey used for tests from 3.0 to 3.1; remove unused RESTeasy test dependency
  • #234: ProviderBase.readFrom() fails to advance past START_ARRAY for MappingIterator with Array input

Changes, other

  • #223: Reading List<List<X>> or List<String[]> never returns when an element isn't an array

Clone this wiki locally