Three Maven modules - jackson2-before (2.22.1), jackson3-after (3.2.1) and a coexistence module with BOTH majors on one classpath - so every claim in the two migration guides is executed rather than asserted. Paired class names make the before/after outputs directly diffable via run-all.sh. Confirms the guides on wire-format equivalence (10-case suite, zero mismatches), classpath coexistence and the collapse of four artifacts into one. Corrects nine points, including that enableDefaultTyping() is still present in Jackson 2.22.1 rather than removed in 2.16, and that the published "after" mapper snippet does not compile.
6.9 KiB
jackson2-to-3-migration
Companion code for the two Jackson migration guides on ankurm.com:
The point of this repo is that nothing here is asserted. Every migration claim is a program that runs on both major versions, so you can diff the outputs.
Jackson 2 2.22.1 (com.fasterxml.jackson.core)
Jackson 3 3.2.1 (tools.jackson.core)
jackson-annotations 2.22 (shared by both)
JDK Temurin 21.0.5
Maven 3.9.9
Layout
| Module | What it is |
|---|---|
jackson2-before/ |
Jackson 2.22.1, with the three extra modules and the -parameters compiler flag it needs — see pom.xml |
jackson3-after/ |
Jackson 3.2.1. One dependency, no modules, no compiler flags — see pom.xml |
coexistence/ |
Both majors on one classpath, used together in one class — see pom.xml and docs/coexistence.md |
Paired examples share a class name across before and after, so their captured
outputs sit side by side and diff cleanly.
Run it
git clone https://ankurm.com/git.app/asmhatre/jackson2-to-3-migration.git
cd jackson2-to-3-migration
./run-all.sh
run-all.sh builds all three modules, runs every example, writes real stdout to
docs/output/<module>-<Class>.txt, and then reports which before/after pairs differ.
Diff any pair yourself:
diff docs/output/before-S07DefaultsThatFlipped.txt \
docs/output/after-S07DefaultsThatFlipped.txt
The examples
| Pair | Migration topic | Write-up |
|---|---|---|
S01MapperConstruction |
new ObjectMapper() → JsonMapper.builder(), and immutability |
docs/s01-mapper-construction.md |
S02ExceptionHierarchy |
JsonProcessingException (checked) → JacksonException (unchecked) |
docs/s02-exception-hierarchy.md |
S03CustomHandlers |
JsonSerializer/JsonDeserializer → ValueSerializer/ValueDeserializer |
docs/s03-custom-handlers.md |
S04ModulesAndRecords |
Four artifacts and a compiler flag collapse to one dependency | docs/s04-modules-and-records.md |
S05DefaultTyping |
activateDefaultTyping moves to the builder |
docs/s05-default-typing.md |
S07DefaultsThatFlipped |
Defaults that changed silently | docs/s07-defaults-that-flipped.md |
S06BothOnOneClasspath |
Both majors in one JVM, one shared annotations jar | docs/coexistence.md |
S08WireFormatEquivalence |
10-case regression suite asserting byte-identical JSON | docs/coexistence.md |
Each write-up shows the Jackson 2 and Jackson 3 output side by side, with the source files linked. Start at docs/README.md for the index, or dive straight into docs/s07-defaults-that-flipped.md — the silent behavioural changes are the ones that cost the most to find later. Raw stdout for every program is in docs/output/.
Corrections to the guides
Building this against real artifacts surfaced several places where the guides are wrong. Each is demonstrated by code.
| # | Claim in the guide | What actually happens |
|---|---|---|
| 1 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) in the "after" snippet |
Does not compile. Not on SerializationFeature in Jackson 3 — moved to tools.jackson.databind.cfg.DateTimeFeature, and already off by default. See after/S01. |
| 2 | JsonMapper.builder().serializationInclusion(NON_NULL) |
No such builder method. Use changeDefaultPropertyInclusion(UnaryOperator). |
| 3 | mapper.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL, ...) |
Two errors: the method is not on the Jackson 3 mapper (builder only), and DefaultTyping is now a top-level enum. See after/S05. |
| 4 | enableDefaultTyping() was "removed in 2.16" |
Still present on ObjectMapper in Jackson 2.22.1, deprecated. before/S05 prints the reflective proof. This matters: the method survives your 2.x upgrades and only breaks at Jackson 3. |
| 5 | import tools.jackson.core.JsonFactory |
Wrong package — it is tools.jackson.core.json.JsonFactory. |
| 6 | "Java baseline: Java 11 minimum" (migration guide) | The comparison post says 17, and 17 is correct for all core modules. The migration guide's Java 11 figure is Jackson 2's. |
| 7 | jackson-annotations version 3.0 / 2.20.0 |
Jackson 3.2.1 resolves jackson-annotations 2.22. There is no released 3.0 — only release candidates. Import tools.jackson:jackson-bom and let it pick. |
| 8 | "catch (IOException) blocks silently stop catching" |
Only when the block also does real I/O. If it contains nothing but Jackson calls, it is a compile error and the compiler catches it for you. Both cases in after/S02. |
| 9 | Removing AUTO_DETECT_CREATORS breaks single-arg constructors |
The constant is gone; the behaviour is not. Demonstrated in the features repo. |
What the guides get right
Worth stating, since the list above is all corrections. These claims were tested and hold:
- The wire format is identical.
S08WireFormatEquivalenceruns 10 cases — records, nested lists, maps,java.time, present and emptyOptional,NON_NULL,@JsonProperty,@JsonFormat, empty collections — through both mappers and asserts string equality. Zero mismatches. Downstream consumers are unaffected by the upgrade. - Both majors coexist on one classpath.
S06BothOnOneClasspathuses both in one class, and each reads the other's output. - Annotations do not change. A single
jackson-annotations-2.22.jarserves both. - Records,
Optionalandjava.timeneed no modules in Jackson 3.before/S04shows a bare Jackson 2 mapper failing on all three;after/S04shows the same code working withJsonMapper.builder().build()and nothing else. - Mapper immutability is real, not advisory.
before/S01mutates a shared mapper after construction and the change sticks;after/S01showsObjectMapperhas zeroset*methods.
Related
- jackson3-by-example — 37 runnable examples for the eight feature posts.