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

57 lines
2.5 KiB
Markdown

# S05 — Default typing
Guides: <https://ankurm.com/jackson-3-migration-guide/> (Step 4) and
<https://ankurm.com/jackson-security-best-practices/>
[`before/S05DefaultTyping.java`](../jackson2-before/src/main/java/com/ankurm/migration/before/S05DefaultTyping.java) ·
[`after/S05DefaultTyping.java`](../jackson3-after/src/main/java/com/ankurm/migration/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:
```java
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.