1
0
Files
jackson2-to-3-migration/docs/s05-default-typing.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.5 KiB

S05 — Default typing

Guides: https://ankurm.com/jackson-3-migration-guide/ (Step 4) and https://ankurm.com/jackson-security-best-practices/

before/S05DefaultTyping.java · after/S05DefaultTyping.java

Two corrections

enableDefaultTyping() was not removed in 2.16. Both guides say it was. It is still on ObjectMapper in Jackson 2.22.1, deprecated. The "before" program prints the reflective check. This changes the migration story: the method survives every 2.x upgrade, so Jackson 3 is where a codebase using it finally fails to compile — which is also the guides' own argument for why the removal is a useful security forcing function.

The remediation snippet is Jackson 2. The security post shows:

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(safeTypeValidator, ObjectMapper.DefaultTyping.NON_FINAL, ...);

Neither half compiles against Jackson 3. activateDefaultTyping is on JsonMapper.Builder only, and DefaultTyping is now a top-level enum in tools.jackson.databind rather than nested in ObjectMapper.

Output

Jackson 2 — jackson2-before

enableDefaultTyping on Jackson 2.22 ObjectMapper : true
written : {"@class":"com.ankurm.migration.before.S05DefaultTyping$Envelope","body":{"@class":"com.ankurm.migration.before.S05DefaultTyping$SafePayload","note":"ok"}}
read    : SafePayload[ok]

Jackson 3 — jackson3-after

enableDefaultTyping    on Jackson 3 ObjectMapper : false
activateDefaultTyping  on Jackson 3 ObjectMapper : false
activateDefaultTyping  on JsonMapper.Builder    : true

written : {"@class":"com.ankurm.migration.after.S05DefaultTyping$Envelope","body":{"@class":"com.ankurm.migration.after.S05DefaultTyping$SafePayload","note":"ok"}}
read    : SafePayload[ok]
rogue   : rejected with InvalidTypeIdException

A third trap, in neither guide: with DefaultTyping.NON_FINAL Jackson writes a type id for the root object too. So the root class has to be in the allowlist as well as the payload hierarchy. Allowlist only the payload base type and the happy path fails, not just the attack path — which looks like a broken validator when it is working correctly.

The final rogue line is the negative test: a real class, present on the classpath, deserialisable in every other respect, refused at type resolution before instantiation.