# jackson3-by-example Runnable companion code for the eight-part **Jackson 3** series on [ankurm.com](https://ankurm.com). Every example is a standalone `main()` you can run on its own. Every line of output in [`docs/`](docs) was produced by [`run-all.sh`](run-all.sh) on the versions below — nothing is transcribed by hand. ``` Jackson 3.2.1 (tools.jackson.core:jackson-databind) jackson-annotations 2.22 (com.fasterxml.jackson.core — deliberately unchanged) JDK Temurin 21.0.5 Maven 3.9.9 ``` ## Run it ```bash git clone https://ankurm.com/git.app/asmhatre/jackson3-by-example.git cd jackson3-by-example ./run-all.sh # compiles, runs all 37 examples, refreshes docs/output/ ``` Or run any single example: ```bash mvn -q compile mvn -q exec:java -Dexec.mainClass=com.ankurm.jackson3.part5polymorphic.F01SerialiseMixedList ``` ## Map: post → code | Post | Package | Examples | Notes & output | |---|---|---|---| | [Jackson 101](https://ankurm.com/jackson-java-tutorial/) | [`part0setup`](src/main/java/com/ankurm/jackson3/part0setup) | [A01](src/main/java/com/ankurm/jackson3/part0setup/A01FirstRoundTrip.java) [A02](src/main/java/com/ankurm/jackson3/part0setup/A02SharedMapperConfiguration.java) [A03](src/main/java/com/ankurm/jackson3/part0setup/A03ThreeProcessingModels.java) | [write-up](docs/part0-setup.md) | | [ObjectMapper Guide](https://ankurm.com/jackson-objectmapper-guide/) | [`part1objectmapper`](src/main/java/com/ankurm/jackson3/part1objectmapper) | [B01](src/main/java/com/ankurm/jackson3/part1objectmapper/B01WriteJson.java) [B02](src/main/java/com/ankurm/jackson3/part1objectmapper/B02ReadJson.java) [B03](src/main/java/com/ankurm/jackson3/part1objectmapper/B03GenericCollections.java) [B04](src/main/java/com/ankurm/jackson3/part1objectmapper/B04PropertyOrdering.java) | [write-up](docs/part1-objectmapper.md) | | [Records, Optionals, Sealed](https://ankurm.com/jackson-java-records-optionals/) | [`part2modernjava`](src/main/java/com/ankurm/jackson3/part2modernjava) | [C01](src/main/java/com/ankurm/jackson3/part2modernjava/C01RecordRoundTrip.java) [C02](src/main/java/com/ankurm/jackson3/part2modernjava/C02OptionalFields.java) [C03](src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java) [C04](src/main/java/com/ankurm/jackson3/part2modernjava/C04SealedAutoDiscovery.java) | [write-up](docs/part2-modern-java.md) | | [Annotations Cheat Sheet](https://ankurm.com/jackson-annotations-guide/) | [`part3annotations`](src/main/java/com/ankurm/jackson3/part3annotations) | [D01](src/main/java/com/ankurm/jackson3/part3annotations/D01RenameAndIgnore.java) [D02](src/main/java/com/ankurm/jackson3/part3annotations/D02InclusionAndFormat.java) [D03](src/main/java/com/ankurm/jackson3/part3annotations/D03AliasAndUnknownFields.java) [D04](src/main/java/com/ankurm/jackson3/part3annotations/D04CreatorsAndUnwrapping.java) | [write-up](docs/part3-annotations.md) | | [Custom Serialisers & Mix-ins](https://ankurm.com/jackson-custom-serializer-mixin/) | [`part4custom`](src/main/java/com/ankurm/jackson3/part4custom) | [E01](src/main/java/com/ankurm/jackson3/part4custom/E01MoneyValueSerializer.java) [E02](src/main/java/com/ankurm/jackson3/part4custom/E02MoneyValueDeserializer.java) [E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java) [E04](src/main/java/com/ankurm/jackson3/part4custom/E04MixinAnnotations.java) [E05](src/main/java/com/ankurm/jackson3/part4custom/E05ValueSerializerDirect.java) | [write-up](docs/part4-custom.md) | | [Polymorphic Deserialisation](https://ankurm.com/jackson-polymorphic-deserialization/) | [`part5polymorphic`](src/main/java/com/ankurm/jackson3/part5polymorphic) | [F01](src/main/java/com/ankurm/jackson3/part5polymorphic/F01SerialiseMixedList.java) [F02](src/main/java/com/ankurm/jackson3/part5polymorphic/F02DeserialiseMixedList.java) [F03](src/main/java/com/ankurm/jackson3/part5polymorphic/F03IncludeStrategies.java) [F04](src/main/java/com/ankurm/jackson3/part5polymorphic/F04UnknownTypeId.java) | [write-up](docs/part5-polymorphic.md) | | [Streaming API & JsonNode](https://ankurm.com/jackson-streaming-api-jsonnode/) | [`part6streaming`](src/main/java/com/ankurm/jackson3/part6streaming) | [G01](src/main/java/com/ankurm/jackson3/part6streaming/G01StreamingParserFilter.java) [G02](src/main/java/com/ankurm/jackson3/part6streaming/G02StreamingGenerator.java) [G03](src/main/java/com/ankurm/jackson3/part6streaming/G03TreeModelNavigation.java) [G04](src/main/java/com/ankurm/jackson3/part6streaming/G04ThreeApproachesMeasured.java) | [write-up](docs/part6-streaming.md) | | [Security Best Practices](https://ankurm.com/jackson-security-best-practices/) | [`part7security`](src/main/java/com/ankurm/jackson3/part7security) | [H01](src/main/java/com/ankurm/jackson3/part7security/H01SafePolymorphismByAnnotation.java) [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java) [H03](src/main/java/com/ankurm/jackson3/part7security/H03PolymorphicTypeValidatorAllowlist.java) [H04](src/main/java/com/ankurm/jackson3/part7security/H04StreamReadConstraints.java) [H05](src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java) | [write-up](docs/part7-security.md) | | — (beyond the posts) | [`beyond`](src/main/java/com/ankurm/jackson3/beyond) | [Y01](src/main/java/com/ankurm/jackson3/beyond/Y01UncheckedExceptions.java) [Y02](src/main/java/com/ankurm/jackson3/beyond/Y02TrailingTokens.java) [Y03](src/main/java/com/ankurm/jackson3/beyond/Y03DateTimeDefaults.java) [Y04](src/main/java/com/ankurm/jackson3/beyond/Y04ImmutableMapperAndReaders.java) [Y05](src/main/java/com/ankurm/jackson3/beyond/Y05CreatorDetection.java) [Y06](src/main/java/com/ankurm/jackson3/beyond/Y06RecyclerPoolTuning.java) | [write-up](docs/beyond.md) | ### Documentation Each page pairs the code with its real captured output and calls out where the post and the library disagree. | Page | Covers | |---|---| | [docs/part0-setup.md](docs/part0-setup.md) | Setup, the shared-mapper rule, three processing models | | [docs/part1-objectmapper.md](docs/part1-objectmapper.md) | Reading, writing, `TypeReference`, property ordering | | [docs/part2-modern-java.md](docs/part2-modern-java.md) | Records, `Optional`, sealed types and auto-discovery | | [docs/part3-annotations.md](docs/part3-annotations.md) | The annotation set, and which advice is now obsolete | | [docs/part4-custom.md](docs/part4-custom.md) | `ValueSerializer`, `ValueDeserializer`, modules, mix-ins | | [docs/part5-polymorphic.md](docs/part5-polymorphic.md) | `@JsonTypeInfo`, and the dropped-discriminator defect in full | | [docs/part6-streaming.md](docs/part6-streaming.md) | Streaming, the tree model, and the three approaches measured | | [docs/part7-security.md](docs/part7-security.md) | Safe polymorphism, allowlists, resource limits | | [docs/beyond.md](docs/beyond.md) | Unchecked exceptions, flipped defaults, pool tuning | Raw stdout for all 37 programs is in [`docs/output/`](docs/output), regenerated by [`run-all.sh`](run-all.sh). ## Corrections to the posts Writing this code against a real Jackson 3.2.1 build surfaced places where the blog snippets do not compile or do not behave as printed. Each is demonstrated by a program, so you can verify rather than take my word for it. | # | Claim in the post | What actually happens | Shown by | |---|---|---|---| | 1 | Maven coordinate `com.fasterxml.jackson.core:jackson-databind:3.1.2` | That artifact does not exist. Jackson 3 is at `tools.jackson.core:jackson-databind`; only `jackson-annotations` keeps the old group ID. | [`pom.xml`](pom.xml) | | 2 | `JsonMapper.builder().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)` | Does not compile. The constant is not on `SerializationFeature` in Jackson 3 — it moved to `tools.jackson.databind.cfg.DateTimeFeature`, and it already defaults to off, so ISO-8601 needs no configuration. | [Y03](src/main/java/com/ankurm/jackson3/beyond/Y03DateTimeDefaults.java) | | 3 | `JsonMapper.builder().serializationInclusion(...)` | No such builder method. The real API is `changeDefaultPropertyInclusion(UnaryOperator)`. | [A02](src/main/java/com/ankurm/jackson3/part0setup/A02SharedMapperConfiguration.java), [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java) | | 4 | `writeValueAsString(List)` emits `paymentType` | It does not. A `List` carries no element type, so the polymorphic serialiser never engages and the discriminator is dropped — and the output then fails to deserialise. Use `writerFor(TypeReference)` or a typed array. | [F01](src/main/java/com/ankurm/jackson3/part5polymorphic/F01SerialiseMixedList.java), [F02](src/main/java/com/ankurm/jackson3/part5polymorphic/F02DeserialiseMixedList.java) | | 5 | Remediate with `mapper.activateDefaultTyping(validator, ...)` | Jackson 3's mapper has no mutators at all — not `enableDefaultTyping`, not `activateDefaultTyping`, not one `set*` method. `activateDefaultTyping` exists only on `JsonMapper.Builder`. | [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java), [H03](src/main/java/com/ankurm/jackson3/part7security/H03PolymorphicTypeValidatorAllowlist.java) | | 6 | `import tools.jackson.core.JsonFactory` | Wrong package. It is `tools.jackson.core.json.JsonFactory`. | [G01](src/main/java/com/ankurm/jackson3/part6streaming/G01StreamingParserFilter.java) | | 7 | Optional needs `jackson-datatype-jdk8`; records need `jackson-module-parameter-names` and `-parameters` | All three are built into `jackson-databind` 3.x. The Jackson 2 module classes do not exist under `tools.jackson`, so leaving the registrations in place is a compile error. | [C01](src/main/java/com/ankurm/jackson3/part2modernjava/C01RecordRoundTrip.java), [C02](src/main/java/com/ankurm/jackson3/part2modernjava/C02OptionalFields.java) | | 8 | Set `FAIL_ON_UNKNOWN_PROPERTIES=false`; use `@JsonIgnoreProperties(ignoreUnknown=true)` to opt out per class | Already false by default in Jackson 3. The annotation now matters only when you deliberately turn strictness back on. | [D03](src/main/java/com/ankurm/jackson3/part3annotations/D03AliasAndUnknownFields.java) | | 9 | Without `@JsonFormat`, `LocalDate` is written as a numeric array | Jackson 2 behaviour. Jackson 3 writes ISO-8601 by default; the annotation is only needed for a non-standard pattern. | [D02](src/main/java/com/ankurm/jackson3/part3annotations/D02InclusionAndFormat.java) | | 10 | Removing `AUTO_DETECT_CREATORS` means single-arg constructors "quietly fail" | The enum constant is gone, but the behaviour is not: a single-argument constructor is still detected as a delegating creator. Annotate anyway, but the upgrade will not break these classes. | [Y05](src/main/java/com/ankurm/jackson3/beyond/Y05CreatorDetection.java) | | 11 | The custom deserialiser uses `p.getCodec().readTree(p)` and `node.get(...)` | `getCodec()` is gone — use `ctxt.readTree(parser)`. And `get()` NPEs on a missing field; `path()` with a defaulting accessor does not. A bare `decimalValue()` on a `MissingNode` throws in Jackson 3 where Jackson 2 returned zero. | [E02](src/main/java/com/ankurm/jackson3/part4custom/E02MoneyValueDeserializer.java), [E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java) | Two smaller ones worth knowing, neither strictly an error in the posts: - A getter-based POJO serialises its properties **alphabetically**; a record serialises in declaration order. The post's sample output for `ProductSummary` shows declaration order. See [B04](src/main/java/com/ankurm/jackson3/part1objectmapper/B04PropertyOrdering.java). - `{"amount":20.00}` deserialises to `BigDecimal` **20.0**, not 20.00 — the scale is lost unless `USE_BIG_DECIMAL_FOR_FLOATS` is enabled. See [E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java). ## Measured, not asserted Three examples produce numbers rather than prose. Figures below are from one run on a 2-core container; re-run them on your own hardware. **Three processing models over a 20 MB / 200k-entry file** ([G04](src/main/java/com/ankurm/jackson3/part6streaming/G04ThreeApproachesMeasured.java)): ``` data binding (readValue) errors=4000 524 ms heap delta 47 MB tree model (readTree) errors=4000 341 ms heap delta 102 MB streaming (JsonParser) errors=4000 78 ms heap delta 1 MB ``` **Streaming a million records** ([G02](src/main/java/com/ankurm/jackson3/part6streaming/G02StreamingGenerator.java)): 30 MB written in 129 ms with a 0 MB heap delta. **RecyclerPool** ([Y06](src/main/java/com/ankurm/jackson3/beyond/Y06RecyclerPoolTuning.java)) — the comparison post suggests restoring the 2.x thread-local pool if you see a regression. On this box the two pools are within noise of each other in both shapes, and the gap moves between runs, so the honest conclusion is that there is no default winner: measure on your own hardware before changing it. What is stable across runs is that turning recycling off entirely is clearly worse — a useful negative control confirming the pool is doing something. ``` 1 thread threadLocalPool 301 ms concurrentDeque 310 ms nonRecycling 496 ms 8 threads threadLocalPool 451 ms concurrentDeque 450 ms nonRecycling 808 ms ``` ## Related - [jackson2-to-3-migration](https://ankurm.com/git.app/asmhatre/jackson2-to-3-migration) — the before/after companion for the two migration guides.