1
0
Files
jackson2-to-3-migration/docs/s01-mapper-construction.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

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_TIMESTAMPS does not exist. The flag moved to tools.jackson.databind.cfg.DateTimeFeature and 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 is changeDefaultPropertyInclusion(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).