1
0
Files
jackson2-to-3-migration/README.md
Ankur f0a7053fc9 Jackson 2 to 3 migration companion code
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.
2026-08-04 23:29:27 +05:30

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/JsonDeserializerValueSerializer/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. S08WireFormatEquivalence runs 10 cases — records, nested lists, maps, java.time, present and empty Optional, 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. S06BothOnOneClasspath uses both in one class, and each reads the other's output.
  • Annotations do not change. A single jackson-annotations-2.22.jar serves both.
  • Records, Optional and java.time need no modules in Jackson 3. before/S04 shows a bare Jackson 2 mapper failing on all three; after/S04 shows the same code working with JsonMapper.builder().build() and nothing else.
  • Mapper immutability is real, not advisory. before/S01 mutates a shared mapper after construction and the change sticks; after/S01 shows ObjectMapper has zero set* methods.