1
0
Files
jackson3-by-example/docs/part2-modern-java.md
Ankur c438afc33b Jackson 3 series companion code
37 runnable examples covering the eight feature posts on ankurm.com, verified
against Jackson 3.2.1 on Temurin 21.0.5. Every output committed under docs/ was
produced by run-all.sh.

Also documents 11 places where the published snippets do not compile or do not
behave as printed against a real Jackson 3 build - most notably that
writeValueAsString(List<Base>) silently drops the polymorphic type discriminator,
so the post's serialised output cannot be read back.
2026-08-04 23:29:26 +05:30

2.8 KiB

Part 2 — Records, Optionals and sealed types

Post: https://ankurm.com/jackson-java-records-optionals/

The single biggest simplification in Jackson 3 lands here. All three features are in core: the project's pom.xml has one dependency, no jackson-datatype-jdk8, no jackson-module-parameter-names, and no -parameters compiler argument.

C01 — records

C01RecordRoundTrip.java

{"productId":101,"productName":"Wireless Keyboard","unitPrice":49.99}
Wireless Keyboard
round-trip equal: true

The post's own dependency block here is wrong twice over: it suggests com.fasterxml.jackson.module:jackson-module-parameter-names:3.1.2, which is both an artifact Jackson 3 does not need and a version that does not exist at that coordinate.

C02 — Optional

C02OptionalFields.java

present : {"customerName":"Alice","middleName":"Marie"}
empty   : {"customerName":"Bob","middleName":null}
absent  : {"customerName":"Bob"}
isPresent: true
missing -> Optional.empty (null? false)

Two behaviours worth committing to memory: an empty Optional serialises as null unless you ask for NON_ABSENT, and a missing property deserialises to Optional.empty() rather than to null, so the field is never null.

C03 — sealed types with an explicit registry

C03SealedWithSubTypes.java

Circle with radius: 5.0
Rectangle 10.0 x 4.0
lossy   : [{"radius":5.0},{"width":10.0,"height":4.0}]
correct : [{"shapeType":"circle","radius":5.0},{"shapeType":"rectangle","width":10.0,"height":4.0}]

Note the lossy line. Serialising a List<Shape> drops the shapeType discriminator, because a List gives Jackson no element type to dispatch on. This is the same defect covered at length in Part 5.

C04 — sealed auto-discovery

C04SealedAutoDiscovery.java

Beyond the post: Jackson 3 reads the permits clause, so @JsonSubTypes can be dropped entirely when each permitted type carries @JsonTypeName. There is no @JsonSubTypes anywhere in that file, and a third subtype added later just works.

Circle                   -> {"shapeType":"circle","radius":5.0}                  -> Circle[radius=5.0]
Rectangle                -> {"shapeType":"rectangle","width":10.0,"height":4.0}  -> Rectangle[width=10.0, height=4.0]
Triangle                 -> {"shapeType":"triangle","base":3.0,"height":6.0}     -> Triangle[base=3.0, height=6.0]

In Jackson 2 the @JsonSubTypes registry had to be maintained in parallel with permits, and drifted whenever someone added a case.