1
0
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
2026-08-04 23:29:26 +05:30
2026-08-04 23:29:26 +05:30
2026-08-04 23:29:26 +05:30
2026-08-04 23:29:26 +05:30
2026-08-04 23:29:26 +05:30

jackson3-by-example

Runnable companion code for the eight-part Jackson 3 series on ankurm.com.

Every example is a standalone main() you can run on its own. Every line of output in docs/ was produced by run-all.sh on the versions below — nothing is transcribed by hand.

Jackson            3.2.1        (tools.jackson.core:jackson-databind)
jackson-annotations 2.22        (com.fasterxml.jackson.core — deliberately unchanged)
JDK                Temurin 21.0.5
Maven              3.9.9

Run it

git clone https://ankurm.com/git.app/asmhatre/jackson3-by-example.git
cd jackson3-by-example
./run-all.sh                       # compiles, runs all 37 examples, refreshes docs/output/

Or run any single example:

mvn -q compile
mvn -q exec:java -Dexec.mainClass=com.ankurm.jackson3.part5polymorphic.F01SerialiseMixedList

Map: post → code

Post Package Examples Notes & output
Jackson 101 part0setup A01 A02 A03 write-up
ObjectMapper Guide part1objectmapper B01 B02 B03 B04 write-up
Records, Optionals, Sealed part2modernjava C01 C02 C03 C04 write-up
Annotations Cheat Sheet part3annotations D01 D02 D03 D04 write-up
Custom Serialisers & Mix-ins part4custom E01 E02 E03 E04 E05 write-up
Polymorphic Deserialisation part5polymorphic F01 F02 F03 F04 write-up
Streaming API & JsonNode part6streaming G01 G02 G03 G04 write-up
Security Best Practices part7security H01 H02 H03 H04 H05 write-up
— (beyond the posts) beyond Y01 Y02 Y03 Y04 Y05 Y06 write-up

Documentation

Each page pairs the code with its real captured output and calls out where the post and the library disagree.

Page Covers
docs/part0-setup.md Setup, the shared-mapper rule, three processing models
docs/part1-objectmapper.md Reading, writing, TypeReference, property ordering
docs/part2-modern-java.md Records, Optional, sealed types and auto-discovery
docs/part3-annotations.md The annotation set, and which advice is now obsolete
docs/part4-custom.md ValueSerializer, ValueDeserializer, modules, mix-ins
docs/part5-polymorphic.md @JsonTypeInfo, and the dropped-discriminator defect in full
docs/part6-streaming.md Streaming, the tree model, and the three approaches measured
docs/part7-security.md Safe polymorphism, allowlists, resource limits
docs/beyond.md Unchecked exceptions, flipped defaults, pool tuning

Raw stdout for all 37 programs is in docs/output/, regenerated by run-all.sh.

Corrections to the posts

Writing this code against a real Jackson 3.2.1 build surfaced places where the blog snippets do not compile or do not behave as printed. Each is demonstrated by a program, so you can verify rather than take my word for it.

# Claim in the post What actually happens Shown by
1 Maven coordinate com.fasterxml.jackson.core:jackson-databind:3.1.2 That artifact does not exist. Jackson 3 is at tools.jackson.core:jackson-databind; only jackson-annotations keeps the old group ID. pom.xml
2 JsonMapper.builder().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) Does not compile. The constant is not on SerializationFeature in Jackson 3 — it moved to tools.jackson.databind.cfg.DateTimeFeature, and it already defaults to off, so ISO-8601 needs no configuration. Y03
3 JsonMapper.builder().serializationInclusion(...) No such builder method. The real API is changeDefaultPropertyInclusion(UnaryOperator). A02, H02
4 writeValueAsString(List<PaymentMethod>) emits paymentType It does not. A List carries no element type, so the polymorphic serialiser never engages and the discriminator is dropped — and the output then fails to deserialise. Use writerFor(TypeReference) or a typed array. F01, F02
5 Remediate with mapper.activateDefaultTyping(validator, ...) Jackson 3's mapper has no mutators at all — not enableDefaultTyping, not activateDefaultTyping, not one set* method. activateDefaultTyping exists only on JsonMapper.Builder. H02, H03
6 import tools.jackson.core.JsonFactory Wrong package. It is tools.jackson.core.json.JsonFactory. G01
7 Optional needs jackson-datatype-jdk8; records need jackson-module-parameter-names and -parameters All three are built into jackson-databind 3.x. The Jackson 2 module classes do not exist under tools.jackson, so leaving the registrations in place is a compile error. C01, C02
8 Set FAIL_ON_UNKNOWN_PROPERTIES=false; use @JsonIgnoreProperties(ignoreUnknown=true) to opt out per class Already false by default in Jackson 3. The annotation now matters only when you deliberately turn strictness back on. D03
9 Without @JsonFormat, LocalDate is written as a numeric array Jackson 2 behaviour. Jackson 3 writes ISO-8601 by default; the annotation is only needed for a non-standard pattern. D02
10 Removing AUTO_DETECT_CREATORS means single-arg constructors "quietly fail" The enum constant is gone, but the behaviour is not: a single-argument constructor is still detected as a delegating creator. Annotate anyway, but the upgrade will not break these classes. Y05
11 The custom deserialiser uses p.getCodec().readTree(p) and node.get(...) getCodec() is gone — use ctxt.readTree(parser). And get() NPEs on a missing field; path() with a defaulting accessor does not. A bare decimalValue() on a MissingNode throws in Jackson 3 where Jackson 2 returned zero. E02, E03

Two smaller ones worth knowing, neither strictly an error in the posts:

  • A getter-based POJO serialises its properties alphabetically; a record serialises in declaration order. The post's sample output for ProductSummary shows declaration order. See B04.
  • {"amount":20.00} deserialises to BigDecimal 20.0, not 20.00 — the scale is lost unless USE_BIG_DECIMAL_FOR_FLOATS is enabled. See E03.

Measured, not asserted

Three examples produce numbers rather than prose. Figures below are from one run on a 2-core container; re-run them on your own hardware.

Three processing models over a 20 MB / 200k-entry file (G04):

data binding (readValue)     errors=4000      524 ms   heap delta    47 MB
tree model (readTree)        errors=4000      341 ms   heap delta   102 MB
streaming (JsonParser)       errors=4000       78 ms   heap delta     1 MB

Streaming a million records (G02): 30 MB written in 129 ms with a 0 MB heap delta.

RecyclerPool (Y06) — the comparison post suggests restoring the 2.x thread-local pool if you see a regression. On this box the two pools are within noise of each other in both shapes, and the gap moves between runs, so the honest conclusion is that there is no default winner: measure on your own hardware before changing it. What is stable across runs is that turning recycling off entirely is clearly worse — a useful negative control confirming the pool is doing something.

1 thread    threadLocalPool 301 ms   concurrentDeque 310 ms   nonRecycling 496 ms
8 threads   threadLocalPool 451 ms   concurrentDeque 450 ms   nonRecycling 808 ms
Description
Runnable companion code for the 8-part Jackson 3 series on ankurm.com. 37 standalone programs on Jackson 3.2.1 / JDK 21 — ObjectMapper, records, Optional, sealed types, annotations, custom ValueSerializers, mix-ins, polymorphism, streaming and the tree model, security hardening — plus corrections where the real Jackson 3 API differs from the posts. Every output in the docs was produced by run-all.sh.
Readme 254 KiB
Languages
Java 99%
Shell 1%