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.
2.3 KiB
S01 — Mapper construction and immutability
Guide: https://ankurm.com/jackson-3-migration-guide/ (Steps 2 and 3)
before/S01MapperConstruction.java ·
after/S01MapperConstruction.java
The guide's "after" snippet does not compile
JsonMapper mapper = JsonMapper.builder()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // no such constant
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.serializationInclusion(JsonInclude.Include.NON_NULL) // no such method
.build();
Two of those four lines are invalid against Jackson 3.2.1:
SerializationFeature.WRITE_DATES_AS_TIMESTAMPSdoes not exist. The flag moved totools.jackson.databind.cfg.DateTimeFeatureand now defaults to off, so ISO-8601 output needs no configuration and there is nothing to disable.JsonMapper.Builder.serializationInclusion(...)does not exist. The real API ischangeDefaultPropertyInclusion(UnaryOperator<JsonInclude.Value>).
The third line is valid but redundant — FAIL_ON_UNKNOWN_PROPERTIES already defaults
to false in Jackson 3.
Output
Jackson 2 — jackson2-before
output : {"id":1,"travelDate":"2026-09-15","seatPreference":"aisle"}
after mutation : { "id" : 1, "travelDate" : "2026-09-15", "seatPreference" : "aisle" }
mutation stuck : true
set*() mutators : 40
Jackson 3 — jackson3-after
output : {"id":1,"travelDate":"2026-09-15","seatPreference":"aisle"}
forked indented : { "id" : 1, "travelDate" : "2026-09-15", "seatPreference" : "aisle" }
original intact : false
set*() mutators : 0 <- mutation is impossible, not merely discouraged
Both produce the same JSON, which is the reassuring part. The interesting lines are the last two of each.
In Jackson 2 the mapper stays mutable forever. mapper.enable(INDENT_OUTPUT) after the
mapper has been injected into a dozen components takes effect for all of them —
mutation stuck : true. Forty set* methods are available to do it with.
In Jackson 3 there are zero. To vary configuration you fork: rebuild() returns a
builder seeded from the existing mapper, and the original is untouched
(original intact : false).