Champollion’s technical signature lies in two points: a hand-written JSON-P state-machine parser designed to minimize allocation, and a compile-time JSON-B codegen via APT that removes runtime reflection. This page details both pipelines.
Modules overview
champollion-jsonb depends on champollion-jsonp. Never the reverse — the binding layer composes on the processing layer.
JSON-P pipeline — pull parser
The parser implements an RFC 8259 state machine driven by plain switch dispatch, with a bit-stack tracking open containers (one bit per nesting level: object or array) and a single-pass number scan (validation and materialization in the same loop — the P13 optimization that overtook Parsson on MEDIUM/LARGE payloads).
Key components (package io.vidocq.champollion.jsonp.internal):
-
ChampollionJsonParser— heart of the state machine, exposes theJsonParserAPI. Nesting is a bit-stack (longwords, one bit per level), not an object stack. -
JsonTokenizerwith two variants:JsonStringTokenizer(in-memoryStringinput) andJsonReaderTokenizer(anyReader, buffered through achar[512]— the P1 sweet spot). Both loop instead of recursing. -
Charset detection in
ChampollionJsonProviderforInputStreaminput: BOM sniffing (UTF-8 / UTF-16 BE/LE / UTF-32 BE/LE), then the RFC 8259 §8.1 null-byte-pattern heuristic when no BOM is present. -
No buffer pool inside
champollion-jsonp: each parse allocates its tokenizer andchar[512](this per-parse fixed cost is why SMALL payloads sit at 0.66× Parsson while MEDIUM/LARGE win — see BENCH.md). The pooling that does exist lives one layer up, inchampollion-jsonb(see below).
The symmetric JsonGenerator pushes through an automatically buffered Writer (BufferedWriter interposed on any non-already-buffered Writer). Precompiled RFC 8259 §7 escape table for control characters.
JSON-B pipeline — runtime introspection (default mode)
-
The write side is
RuntimeBindingRegistry, which resolves aBindingWriteronce per class; the read side isRuntimeReadRegistry, resolving aBindingReader. Both cache in a lock-freeClassValue, plus aConcurrentHashMapkeyed by type name for parameterized types (generic collections). -
Property access goes through
MethodHandles.publicLookup()— record accessors and bean getters/setters arepublic, so they resolve withoutsetAccessible(true)and without requiring the consumer toopensits package; a plain-reflection fallback covers the rare cases wherepublicLookupcannot resolve (e.g. records in non-exported packages). -
Customization (
@JsonbProperty,@JsonbDateFormat,@JsonbTypeAdapter, etc.) is baked into the cachedBindingWriter/BindingReaderonce.
JSON-B pipeline — static codegen (APT)
This is Champollion’s identity-defining strength. Rather than introspecting at runtime, the champollion-codegen-apt annotation processor emits at compile time a <Type>$$Binding.java that implements JsonbBinding<T> directly, without reflection.
Key traits:
-
Generated Java code, no bytecode in
champollion-codegen-apt. Bytecode comes viajavacon the generated source — debuggable, readable. -
Class-File API (JEP 484) considered for secondary passes (precomputed UTF-8 byte-array constants in bytecode). No Byte Buddy, no ASM.
-
META-INF/services/io.vidocq.champollion.jsonb.spi.JsonbBinding: the APT accumulates every generated binding in this services file;ChampollionJsonbdiscovers them viaServiceLoaderat startup (collision strategy: first registered wins, deterministic source order — consistent with Java Modulesprovides … withsemantics). -
Runtime fallback: if no static binding is found for a type,
ChampollionJsonbsilently falls back to M4 introspection (RuntimeBindingRegistry/RuntimeReadRegistry). To audit which types are statically bound, inspect theMETA-INF/services/io.vidocq.champollion.jsonb.spi.JsonbBindingentries in your jar. -
AOT-friendly: zero reflection ⇒ GraalVM
native-imagewith no config, Leyden CDS happy. -
Optimizations in the generated binding: property names as precomputed UTF-8
byte[]constants, stable write order, grouped writes (writeStartObject+ firstwritemerged via thewriteStringfast-path).
champollion-codegen-maven-plugin is a thin Maven orchestrator for types you cannot annotate (external or inherited): for each record FQN listed explicitly in <targets>, it emits a <FQN>$$Trigger.java source annotated @JsonbStatic and runs javac with JsonbStaticProcessor enabled. There is no classpath scanning — the "scan all records on the classpath" path is planned but not shipped. For types annotated @JsonbStatic directly in your sources, the plugin is not needed: the APT runs during normal compilation.
Virtual threads
-
No
synchronizedanywhere on the hot paths — caches areClassValue/ConcurrentHashMap, safe underExecutors.newVirtualThreadPerTaskExecutor(). -
Per-call state (current config, serialization / deserialization contexts) lives in explicit per-operation objects (
ChampollionSerializationContext/ChampollionDeserializationContext) passed down the call chain — no application-visible thread-local state, no ambient context mechanism. -
The one piece of thread-local state is internal:
ChampollionJsonbkeeps a per-threadChampollionJsonParser(P9) sofromJsondoes not reallocateJsonTokenizer + char[512] + Dequeon every call. It works becausechampollion-jsonpgrants a Java Modules-qualified export:exports io.vidocq.champollion.jsonp.internal to io.vidocq.champollion.jsonb(cf. BENCH.md, P9 entry).
AOT compatibility
GraalVM |
Static mode: zero config required (no reflection). Runtime mode: requires reachability metadata for bound classes. |
Leyden CDS |
Static mode: fully supported; AppCDS preloads |
Java Module System |
Strict |
Why this architecture?
-
Zero dependency: Champollion embeds in any runtime without dragging Parsson, Yasson, Jackson, or their transitives. Minimal attack surface, fast startup.
-
Codegen over reflection: predictable (no first-hit surprise), AOT-friendly, debuggable (the generated code is readable).
-
Strict Java Modules: strong encapsulation,
internal.*may break between versions without notice. -
Virtual threads: modern REST servers (Cassini) run thousands of concurrent threads — no contention point in the serializer.