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.
This commit is contained in:
103
README.md
Normal file
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# jackson2-to-3-migration
|
||||
|
||||
Companion code for the two Jackson migration guides on [ankurm.com](https://ankurm.com):
|
||||
|
||||
- [Jackson 2 to Jackson 3 Migration Guide](https://ankurm.com/jackson-3-migration-guide/)
|
||||
- [Jackson 3 vs Jackson 2: Complete Comparison](https://ankurm.com/jackson-3-vs-jackson-2/)
|
||||
|
||||
The point of this repo is that nothing here is asserted. Every migration claim is a
|
||||
program that runs on **both** major versions, so you can diff the outputs.
|
||||
|
||||
```
|
||||
Jackson 2 2.22.1 (com.fasterxml.jackson.core)
|
||||
Jackson 3 3.2.1 (tools.jackson.core)
|
||||
jackson-annotations 2.22 (shared by both)
|
||||
JDK Temurin 21.0.5
|
||||
Maven 3.9.9
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
| Module | What it is |
|
||||
|---|---|
|
||||
| [`jackson2-before/`](jackson2-before) | Jackson 2.22.1, with the three extra modules and the `-parameters` compiler flag it needs — see [pom.xml](jackson2-before/pom.xml) |
|
||||
| [`jackson3-after/`](jackson3-after) | Jackson 3.2.1. One dependency, no modules, no compiler flags — see [pom.xml](jackson3-after/pom.xml) |
|
||||
| [`coexistence/`](coexistence) | **Both majors on one classpath**, used together in one class — see [pom.xml](coexistence/pom.xml) and [docs/coexistence.md](docs/coexistence.md) |
|
||||
|
||||
Paired examples share a class name across `before` and `after`, so their captured
|
||||
outputs sit side by side and diff cleanly.
|
||||
|
||||
## Run it
|
||||
|
||||
```bash
|
||||
git clone https://ankurm.com/git.app/asmhatre/jackson2-to-3-migration.git
|
||||
cd jackson2-to-3-migration
|
||||
./run-all.sh
|
||||
```
|
||||
|
||||
`run-all.sh` builds all three modules, runs every example, writes real stdout to
|
||||
`docs/output/<module>-<Class>.txt`, and then reports which before/after pairs differ.
|
||||
Diff any pair yourself:
|
||||
|
||||
```bash
|
||||
diff docs/output/before-S07DefaultsThatFlipped.txt \
|
||||
docs/output/after-S07DefaultsThatFlipped.txt
|
||||
```
|
||||
|
||||
## The examples
|
||||
|
||||
| Pair | Migration topic | Write-up |
|
||||
|---|---|---|
|
||||
| `S01MapperConstruction` | `new ObjectMapper()` → `JsonMapper.builder()`, and immutability | [docs/s01-mapper-construction.md](docs/s01-mapper-construction.md) |
|
||||
| `S02ExceptionHierarchy` | `JsonProcessingException` (checked) → `JacksonException` (unchecked) | [docs/s02-exception-hierarchy.md](docs/s02-exception-hierarchy.md) |
|
||||
| `S03CustomHandlers` | `JsonSerializer`/`JsonDeserializer` → `ValueSerializer`/`ValueDeserializer` | [docs/s03-custom-handlers.md](docs/s03-custom-handlers.md) |
|
||||
| `S04ModulesAndRecords` | Four artifacts and a compiler flag collapse to one dependency | [docs/s04-modules-and-records.md](docs/s04-modules-and-records.md) |
|
||||
| `S05DefaultTyping` | `activateDefaultTyping` moves to the builder | [docs/s05-default-typing.md](docs/s05-default-typing.md) |
|
||||
| `S07DefaultsThatFlipped` | Defaults that changed silently | [docs/s07-defaults-that-flipped.md](docs/s07-defaults-that-flipped.md) |
|
||||
| `S06BothOnOneClasspath` | Both majors in one JVM, one shared annotations jar | [docs/coexistence.md](docs/coexistence.md) |
|
||||
| `S08WireFormatEquivalence` | 10-case regression suite asserting byte-identical JSON | [docs/coexistence.md](docs/coexistence.md) |
|
||||
|
||||
Each write-up shows the Jackson 2 and Jackson 3 output side by side, with the source
|
||||
files linked. Start at [docs/README.md](docs/README.md) for the index, or dive straight
|
||||
into [docs/s07-defaults-that-flipped.md](docs/s07-defaults-that-flipped.md) — the silent
|
||||
behavioural changes are the ones that cost the most to find later. Raw stdout for every
|
||||
program is in [docs/output/](docs/output).
|
||||
|
||||
## Corrections to the guides
|
||||
|
||||
Building this against real artifacts surfaced several places where the guides are
|
||||
wrong. Each is demonstrated by code.
|
||||
|
||||
| # | Claim in the guide | What actually happens |
|
||||
|---|---|---|
|
||||
| 1 | `.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)` in the "after" snippet | Does not compile. Not on `SerializationFeature` in Jackson 3 — moved to `tools.jackson.databind.cfg.DateTimeFeature`, and already off by default. See [`after/S01`](jackson3-after/src/main/java/com/ankurm/migration/after/S01MapperConstruction.java). |
|
||||
| 2 | `JsonMapper.builder().serializationInclusion(NON_NULL)` | No such builder method. Use `changeDefaultPropertyInclusion(UnaryOperator)`. |
|
||||
| 3 | `mapper.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL, ...)` | Two errors: the method is not on the Jackson 3 mapper (builder only), and `DefaultTyping` is now a top-level enum. See [`after/S05`](jackson3-after/src/main/java/com/ankurm/migration/after/S05DefaultTyping.java). |
|
||||
| 4 | `enableDefaultTyping()` was "removed in 2.16" | Still present on `ObjectMapper` in Jackson 2.22.1, deprecated. [`before/S05`](jackson2-before/src/main/java/com/ankurm/migration/before/S05DefaultTyping.java) prints the reflective proof. This matters: the method survives your 2.x upgrades and only breaks at Jackson 3. |
|
||||
| 5 | `import tools.jackson.core.JsonFactory` | Wrong package — it is `tools.jackson.core.json.JsonFactory`. |
|
||||
| 6 | "Java baseline: Java 11 minimum" (migration guide) | The comparison post says 17, and 17 is correct for all core modules. The migration guide's Java 11 figure is Jackson 2's. |
|
||||
| 7 | `jackson-annotations` version `3.0` / `2.20.0` | Jackson 3.2.1 resolves `jackson-annotations` **2.22**. There is no released 3.0 — only release candidates. Import `tools.jackson:jackson-bom` and let it pick. |
|
||||
| 8 | "`catch (IOException)` blocks silently stop catching" | Only when the block also does real I/O. If it contains nothing but Jackson calls, it is a **compile error** and the compiler catches it for you. Both cases in [`after/S02`](jackson3-after/src/main/java/com/ankurm/migration/after/S02ExceptionHierarchy.java). |
|
||||
| 9 | Removing `AUTO_DETECT_CREATORS` breaks single-arg constructors | The constant is gone; the behaviour is not. Demonstrated in the [features repo](https://ankurm.com/git.app/asmhatre/jackson3-by-example). |
|
||||
|
||||
## What the guides get right
|
||||
|
||||
Worth stating, since the list above is all corrections. These claims were tested and hold:
|
||||
|
||||
- **The wire format is identical.** `S08WireFormatEquivalence` runs 10 cases — records,
|
||||
nested lists, maps, `java.time`, present and empty `Optional`, `NON_NULL`,
|
||||
`@JsonProperty`, `@JsonFormat`, empty collections — through both mappers and asserts
|
||||
string equality. Zero mismatches. Downstream consumers are unaffected by the upgrade.
|
||||
- **Both majors coexist on one classpath.** `S06BothOnOneClasspath` uses both in one
|
||||
class, and each reads the other's output.
|
||||
- **Annotations do not change.** A single `jackson-annotations-2.22.jar` serves both.
|
||||
- **Records, `Optional` and `java.time` need no modules in Jackson 3.** `before/S04`
|
||||
shows a bare Jackson 2 mapper failing on all three; `after/S04` shows the same code
|
||||
working with `JsonMapper.builder().build()` and nothing else.
|
||||
- **Mapper immutability is real, not advisory.** `before/S01` mutates a shared mapper
|
||||
after construction and the change sticks; `after/S01` shows `ObjectMapper` has zero
|
||||
`set*` methods.
|
||||
|
||||
## Related
|
||||
|
||||
- [jackson3-by-example](https://ankurm.com/git.app/asmhatre/jackson3-by-example) — 37 runnable examples for the eight feature posts.
|
||||
Reference in New Issue
Block a user