1
0
Files
jackson2-to-3-migration/docs/s02-exception-hierarchy.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

48 lines
2.1 KiB
Markdown

# S02 — The exception hierarchy
Guide: <https://ankurm.com/jackson-3-vs-jackson-2/> ("JacksonException Is Now Unchecked")
[`before/S02ExceptionHierarchy.java`](../jackson2-before/src/main/java/com/ankurm/migration/before/S02ExceptionHierarchy.java) ·
[`after/S02ExceptionHierarchy.java`](../jackson3-after/src/main/java/com/ankurm/migration/after/S02ExceptionHierarchy.java)
The guide calls this the most dangerous change and it is right. But the failure mode is
narrower than "every `catch (IOException)` silently stops catching", and the difference
decides how much of your codebase you actually have to audit.
**Case A — the try block contains only Jackson calls.** `catch (IOException)` becomes a
*compile error*: "exception java.io.IOException is never thrown in body of corresponding
try statement". The compiler finds these for you. No audit needed.
**Case B — the try block also does real I/O.** `IOException` is still reachable from the
I/O, so the catch block compiles, and it silently stops covering the Jackson call.
Nothing warns you. This is the shape that reaches production, and it is what the
example runs.
## Output
**Jackson 2 — `jackson2-before`**
```
JsonProcessingException extends IOException : true
caught by catch (IOException) : JsonParseException
writeValueAsString declares : [class com.fasterxml.jackson.core.JsonProcessingException]
```
**Jackson 3 — `jackson3-after`**
```
JacksonException extends RuntimeException : true
JacksonException extends IOException : false
ESCAPED catch (IOException) : StreamReadException
caught by catch (Jackson...) : StreamReadException
writeValueAsString declares : [class tools.jackson.core.JacksonException] <- nothing checked
```
So the audit target is narrower and more specific than "grep for `catch (IOException)`":
it is `catch (IOException)` blocks that contain **both** I/O and a Jackson call. Those
are the ones the compiler cannot help with.
Note the last line of each. Jackson 3 still *declares* `JacksonException` on
`writeValueAsString`, but since it is unchecked, callers are not forced to handle it —
which is what lets Jackson calls sit inside lambdas without a wrapper.