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.
12 KiB
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
Per-part write-ups with the captured output live in docs/.
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
ProductSummaryshows declaration order. See B04. {"amount":20.00}deserialises toBigDecimal20.0, not 20.00 — the scale is lost unlessUSE_BIG_DECIMAL_FOR_FLOATSis 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 424 ms heap delta 46 MB
tree model (readTree) errors=4000 290 ms heap delta 101 MB
streaming (JsonParser) errors=4000 79 ms heap delta 0 MB
Streaming a million records (G02): 30 MB written in 124 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 that advice holds under concurrency and is a wash single-threaded:
1 thread threadLocalPool 317 ms concurrentDeque 317 ms nonRecycling 342 ms
8 threads threadLocalPool 328 ms concurrentDeque 416 ms nonRecycling 935 ms
Related
- jackson2-to-3-migration — the before/after companion for the two migration guides.