Repository navigation
Releases: jwcarman/codec
Release list
0.10.0
Breaking changes
codec-core moved from org.jwcarman.codec.spi to org.jwcarman.codec.
Migration is one find-and-replace:
org.jwcarman.codec.spi -> org.jwcarman.codec
No type, member or behaviour changed, and the Automatic-Module-Name is unchanged, so nothing but the import line moves.
Codec, CodecFactory, TypeRef and the five exception types now live in the package their module is named for, matching every other module (org.jwcarman.codec.crypto, .versioned, .transform.compress and the rest), none of which carries a role suffix. The spi name described only CodecFactory, the one interface a backend implements; the other seven types are what callers use.
Fixed
TypeRef captured the wrong type argument through an indirect subclass. The constructor read the immediate superclass's first type argument, which is only TypeRef's T when the subclass extends TypeRef directly. If you wrapped TypeRef in an abstraction of your own — abstract class EnvelopeCodec<E> extends TypeRef<Envelope<E>> — you got a codec built for the wrong type, and the failure surfaced far away as a ClassCastException or a decode into the wrong shape.
| Declaration | Before | After |
|---|---|---|
class Mid<A, B> extends TypeRef<B> |
captured A |
captures B |
class Wrapping<X> extends TypeRef<List<X>> |
captured X |
captures List<X> |
class Concrete extends TypeRef<String>, new Concrete() {} |
ClassCastException |
captures String |
T is now resolved through the whole hierarchy, following a binding that leads to another binding (class Nested<X> extends Mid<X, List<X>>) and substituting into parameterized types, generic arrays and wildcard bounds alike, since a half-substituted type claims to be concrete while a type variable is still sitting in it.
Extending TypeRef raw binds nothing to T, which leaves T standing for itself and is rejected by the same IllegalArgumentException that already caught a captured type variable.
If you only ever wrote new TypeRef<List<Person>>() {} directly, this bug never affected you.
Documentation
rawClass() no longer claims its unchecked cast is "sound by construction" unconditionally. That holds for a reference whose T the compiler established — captured, or built by listOf and the other typed combinators — and not for one from parameterized, whose T the caller asserts.
parameterized documents one shape it cannot build: an inner class of a generic outer class. A class literal has already discarded the outer's arguments, so the built type is not equal to the captured one and will not find it as a cache key. The class-level cache-key guarantee names this exception.
Requirements
Java 25. Spring Boot 4.1.x for the starter and auto-configuration modules.
Full changelog: https://github.com/jwcarman/codec/blob/main/CHANGELOG.md
0.9.0
Added
TypeRef.listOf,setOf,optionalOf,mapOfandparameterizedbuild a
parameterized type reference from the references of its arguments, so generic
code can create aCodec<List<O>>orCodec<Envelope<O>>from a caller's
TypeRef<O>. A built reference equals the same type captured by an anonymous
subclass.parameterizedtakes the class asClass<? super T>, so the
compiler rejects a class that is not the declared type's; a class with no type
parameters, a wrong argument count and primitive arguments are rejected at
construction. The argument list against the declared type's own arguments is
the caller's to get right
Changed
codec-spring-boot-starternow bringscodec-versionedalong with
codec-core,codec-transformsandcodec-autoconfigure: the module has
no dependencies of its own, and versioned storage is part of the toolkit the
starter promises
Fixed
codec-kafkano longer resolves thelz4-java1.10.1 andzstd-jni1.5.6 that
kafka-clientsdeclares: the parent now manages both at the versions codec
ships (1.11.2 and 1.5.7), which closes GHSA-xx22-p4ch-683r (CVE-2026-59949, a
JVM crash in lz4-java's native XXHash on invalid array ranges) on that path
0.8.0
Breaking changes
- Every failure
Codec.encodeandCodec.decodereport is now a
CodecExceptionfromcodec-core, in one of four families keyed to what the
caller does next:InvalidValueException(fix the value),
InvalidPayloadException(quarantine),UnsupportedFormatException(hold for
a newer reader),TransientCodecException(retry). Anything catching
IllegalArgumentException,IllegalStateException,UncheckedIOException,
ClassCastException, or a Jackson/Gson/JSON-B/Fory/protobuf exception from
encode/decodemust catch the family (orCodecException) instead; the
library's exception is still there as the cause. Construction-time exceptions
are unchanged.codec-crypto'sDecryptionException,KeyAccessException
andEncryptionExceptionkeep their names and messages and now extend
InvalidPayloadException,TransientCodecExceptionand
TransientCodecExceptionrespectively; an unknown envelope version or
algorithm id is nowUnsupportedFormatException, not a rejection.
codec-versioned'sUnknownVersionExceptionnow extends
UnsupportedFormatExceptionand is no longer aVersionedFormatException.
See the new Handling failures
guide. Base32Codec.standard()and.hex()decode canonically: upper case only, and
non-zero trailing bits in the final symbol are rejected (MZ======is an
error;MY======isf). Callers who fed lower-case input opt back in with
.caseInsensitive()
Changed
- The build compiles with
-Xlint:all,-processing,-unchecked -Werror: any javac
warning in an enabled category fails compilation.uncheckedis off because
TypeRef.rawClass()is the one unchecked cast the type system cannot express;
no other is permitted. The Gson, Protobuf and Fory backends no longer carry
unchecked casts, and every exception class declares aserialVersionUID codec-gsonencodes by the codec's declared type (Gson.toJson(value, type))
rather than the value's runtime class, matching what decode already did; a
polymorphic value now serialises as its declaredTcodec-fory: Apache Fory 1.1.0 → 1.7.1.ForyCodecFactory.of(...)takes Fory's
defaults, and Fory's default changed from schema-consistent to compatible mode
in 1.2.0 (without a release note), so payloads now carry their class schema:
a reader whose class has gained or lost a field decodes correctly where
schema-consistent mode silently returned a mis-shifted object. It costs a few
bytes per class per message — a lone four-field record roughly doubles, a
100-item order grows 2%; throughput is unchanged. Payloads written by earlier
releases still decode, but bytes written by this release cannot be read by a
schema-consistent Fory. Callers who want a specific mode build their own
ThreadSafeForyand pass it to the constructor. Per Fory's security guidance
the helper also setsdeserializeUnknownClass(false), so a payload naming an
unregistered class is rejected rather than materialised as an anonymous structJacksonCodecFactory,Jackson2CodecFactory, andGsonCodecFactorynow reject a
nullengine orTypeRefwith a labelledNullPointerException, matching the
JSON-B and Fory factories;CompressionStreamCodeclikewise rejectsnullon
encodeanddecodebefore touching a stream, matching the other transforms
Added
TypeRef.rawClass(): the erased class of the captured type, typedClass<T>,
so a backend can use the checkedClass.castinstead of an unchecked cast of
its own. It is the single unchecked cast in the codebase and its Javadoc says socodec-versioned:VersionedCodecprefixes each payload with a magic and
version header and dispatches decoding on it, so a storage strategy can change
without a flag day — old versions stay registered and readable while an
explicit write version makes a two-phase rollout possiblecodec-benchmarks: JMH benchmarks for every compression transform (deflate
and zstd at several levels), encoding, backend (a small record and a
100-item object graph), and the envelope-encryption strategies (not
published), with the results and a benchmarks page in the docsBase32Codec.of(alphabet)andof(alphabet, pad): a strict codec over any
32-symbol ASCII alphabet, withcaseInsensitive()andaliasing(...)as
decode-side opt-ins;crockford()(ULIDs),zBase32()andgeohash()
presets. The encode and decode loops are specialised for whole groups, about
4x the throughput of the previous implementation
Documentation
- Fixed a newcomer-walkthrough review's findings: the Composition guide's
chaining sample and theCodec.andThenJavadoc snippet now build
EnvelopeCodec(there is noAesCodec); the landing page states the Java
25 / Spring Boot 4.x requirement; the landing page and Getting Started link
Apache Kafka and Spring Data Redis; the Guides nav orders Handling Failures
before Evolving Stored Formats; the Kafka guide covers
RecordDeserializationException/ErrorHandlingDeserializerand declares
itsconfigvariable; the Spring Boot guide states what happens with no
backend on the classpath; and the Redis guide turns its auto-configuration
prerequisites into a checklist and adds acodec.redis.cache.*properties
table - Benchmarks re-run on 2026-09-06 with Fory 1.7.1, zstd-jni 1.5.7-15, and
lz4-java 1.11.2: zstd decodes 1 MB payloads 18–29% faster at every level, the
backend numbers and encoded sizes reflect Fory's compatible mode, and the Fory
guide gains a schema-evolution section - Compression claims corrected against the benchmarks: the JDK codecs are
fastest on payloads under a few hundred bytes, zstd's default level is not
smaller than gzip on prose, and LZ4-HC is a decode-side optimisation - New guide, Evolving Stored Formats:
howcodec-versionedworks, a worked upgrade, the fleet rollout order, and
what belongs inside the versioned codec versus outside it - The aggregated Javadoc for every module is published with the site at
jwcarman.github.io/codec/api, one set
per release, built from the release tag
0.7.0
Breaking changes
- The built-in transforms moved out of
codec-coreinto a new
codec-transformsmodule, and into packages by kind:
org.jwcarman.codec.transform.compress(CompressionStreamCodec,
GzipCodec,DeflateCodec) andorg.jwcarman.codec.transform.encoding
(Base64Codec).codec-coreis now the SPI alone. Migration: add
codec-transforms(the Spring Boot starter already includes it) and update
the imports.codec-zstdnow depends oncodec-transforms.
Added
codec-kafka:CodecSerializer,CodecDeserializer, andCodecSerdeadapt
any codec to Kafka's client serialization interfaces; tombstones map to
nullin both directionscodec-spring-data-redis:CodecRedisSerializeradapts any codec to Spring
Data Redis'sRedisSerializer(and, viaserializationPair(), the cache
abstraction); Spring Boot auto-configuration serializes the cache values named
incodec.redis.cache.cachesthrough the application'sCodecFactoryCodec.nullSafe(): an explicit wrapper that mapsnulltonullin both
directions for integrations whose contract treatsnullas absentCodec.xmap(forward, backward): derive a codec for another type from an
existing one — the domain-type-versus-wire-type tool for backends that only
serialize generated or registered classesStringCodecincodec-transforms(transform.text): text as its own bytes
in any charset, with strict decodingChecksumCodecincodec-transforms(transform.checksum): a 32-bit
checksum trailer for corruption detection, CRC-32C by defaultHexCodecincodec-transforms(RFC 4648 base16): lower- or upper-case
output, strict case-insensitive decodingBase32Codecincodec-transforms(RFC 4648 §6 Base32 and §7 base32hex),
strict case-insensitive decodingcodec-lz4: LZ4 frame-format compression transform (Lz4Codec) with fast and
high-compression (LZ4-HC) modes and the same decompression-bomb cap as the
built-in transforms
0.6.0
Four new modules and a text-safe transform. Nothing in the existing modules' APIs or wire formats changed; codec-crypto's frozen 0.5.0 wire format is unchanged.
Added
codec-fory: Apache Fory backend (ForyCodecFactory) for fast JVM-only
binary serialization; class registration is mandatory and never relaxed by
the module. Spring Boot auto-configuration activates only when the application
defines aThreadSafeForybean, and then takes precedence over every
classpath-detected backendcodec-jsonb: Jakarta JSON Binding backend (JsonbCodecFactory) with Spring
Boot auto-configuration; backend precedence is now Jackson 3 → Jackson 2 →
Gson → JSON-B → Protobufcodec-zstd: Zstandard compression transform (ZstdCodec) with configurable
level and the same decompression-bomb cap as the built-in transformsBase64Codecincodec-core: a text-safe transform (basic, URL-safe, URL-safe
without padding, MIME) for chains whose output must live in text columns,
JSON strings, or URLs- Tests proving CBOR, Smile, YAML, and XML work as Jackson backends by mapper
swap, and a Jackson Dataformats guide documenting it
Requirements
- Java 25.
codec-zstdbundles native libraries (via zstd-jni) for the common platforms;codec-forygenerates serializers at runtime and requires class registration.
0.5.0
Added
codec-crypto — envelope encryption as a Codec<byte[]> transform. New module, first release.
Codec<Order> codec = factory.create(Order.class)
.andThen(new GzipCodec())
.andThen(EnvelopeCodec.builder(provider).build());- AES-256-GCM with a versioned, self-describing wire format; the full header is authenticated as GCM associated data.
- Pluggable key management through the
DataKeyProviderSPI — an in-process JCE provider ships (JceDataKeyProvider, AES-KW/RFC 3394 wrapped data keys); remote KMS providers (AWS KMS, Vault, GCP KMS) are implemented by consumers against the documented contract. DataKeyStrategySPI for data-key lifecycle: a fresh key per message by default (nonce-misuse-immune by construction), withBoundedDataKeyStrategyas an opt-in cache bounded by message count and age.- Decode-side key admission (
allowsKeyIdon the provider, or an explicit builder predicate) runs before any unwrap, so a KMS the application is merely permitted to use cannot be turned into a forgery oracle. - A clear error taxonomy:
DecryptionExceptionmeans the data is bad;KeyAccessExceptionmeans the key infrastructure is unavailable — never quarantine data on the latter. - Optional
java.security.Providerinjection on both builders, resolved and fail-fast checked at build time, so a FIPS-validated provider can be pinned per instance. - Zero external dependencies:
codec-cryptodepends oncodec-coreonly.
Compress-then-encrypt is the correct composition order (.andThen(gzip).andThen(crypto)); the guide documents this and the CRIME/BREACH caveat.
Documentation: Encryption guide · Threat model
Assurance
This is new cryptographic code. Before release it was measured against external references and mechanical adversaries, not only against itself:
- Known-answer tests against NIST CAVP GCM vectors and the RFC 3394 key-wrap vector, run through the production code paths.
- Jazzer fuzz targets on the decoder with a committed seed corpus (replayed in every build) and a
-Pfuzzprofile for live fuzzing; ~48M executions with no findings. - PIT mutation testing gates the build: 149/149 mutants killed, verified stable across repeated runs.
- SpotBugs with find-sec-bugs at effort Max / threshold Low, clean. One documented exclusion: the analyzer does not recognize AES key wrap as integrity-protected (RFC 3394's ICV is verified on every unwrap).
- Two adversarial design reviews and a whole-branch final review.
Not yet done: independent human review. No outside cryptographer has examined this module. The threat model page carries the checklist for one.
Known limitations — read before storing encrypted data
Straight from the threat model, in priority order:
- Ciphertext substitution between records is not defended by default. Associated data is fixed per codec instance, so under one codec and KEK an attacker with datastore write access can move record A's ciphertext into record B's row and decode succeeds. Use a distinct codec per context where rows must not be interchangeable, or bind identity inside the plaintext and verify after decode.
- AES-GCM is not key-committing. Algorithm id
0x02is reserved for a committing suite; it is not built. The single-wrapped-DEK envelope with keyId admission does not expose the multi-recipient surface that motivated key commitment elsewhere, but confirm this for your provider topology. - Random nonces under
BoundedDataKeyStrategy: up to 2^24 messages per data key (recommended 2^20). In environments that snapshot and clone VMs, duplicated RNG state can repeat a nonce under a shared cached key — prefer the defaultDirectDataKeyStrategythere, or roll on resume. - FIPS providers: the build-time probe verifies the transform name only. A provider that rejects a key or RNG at
initsurfaces on first use, not atbuild(). - Data keys are not destroyed: they live as
SecretKeySpecinstances for the lifetime of theDataKey(and the bounded cache); only the transient generation buffer is zeroed. - Timing side channels are explicitly out of scope; the uniform cryptographic-failure message covers exception content only.
Also in this release
- CI now enforces Apache 2.0 license headers on every module, including the parentless BOM.
- SonarCloud quality gate clean with zero open issues.
Requirements
- Java 25.
codec-cryptouses only JDK-provided JCE primitives (AES/GCM/NoPadding, AES/KW/NoPadding).
0.4.0
Breaking changes
codec-bom no longer inherits from codec-parent, and no longer re-exports Spring Boot's dependency management.
A child inherits its parent's dependencyManagement, and importing a BOM pulls in that BOM's effective dependencyManagement — after inheritance. Because codec-parent imports spring-boot-dependencies for its own build, codec-bom was re-exporting all of it.
Measured on 0.3.0:
| entries | |
|---|---|
exported by codec-bom |
1921 |
| Codec's own | 7 |
leaked from spring-boot-dependencies |
1914 |
Importing codec-bom therefore overrode your versions for JUnit, AssertJ, Jackson, Guava, Netty and much else — silently, and in a way that surfaced far from the BOM. One consumer hit a NoSuchMethodError at JUnit test discovery caused by a partially-downgraded JUnit, which resolves cleanly and fails only at runtime.
This affected 0.1.0, 0.2.0 and 0.3.0. The BOM now manages exactly the seven Codec artifacts.
If you import codec-bom, check whether any version you relied on was actually coming from it. Anything outside org.jwcarman.codec now needs its own pin — most likely your test stack, since Spring Boot's JUnit and AssertJ versions were the ones being leaked.
Fixed
codec-core's published description no longer claims auto-configuration, which moved tocodec-autoconfigurein 0.2.0.
Internal
- The publish workflow passes
-DprocessAllModules=truetoversions:set. A parentless BOM is skipped otherwise, which would publish it still carrying-SNAPSHOT. - The release tag is passed through
env:rather than interpolated into the shell command.
0.3.0
Breaking changes
Unused dependencies were removed from the published compile surface. The parent pom declared slf4j-api in <dependencies>, so it landed on every artifact including codec-bom. codec-core additionally carried spring-boot-autoconfigure, left over from before auto-configuration moved into codec-autoconfigure.
Neither was used: nothing in the codebase logs, and codec-core has no Spring imports.
codec-core now publishes with zero transitive dependencies.
If your build was relying on either of these arriving transitively through Codec, declare it directly:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>Nothing in Codec's own API changed. This affects only what lands on your classpath.
Changed
codec-jackson2declaresjackson-coreexplicitly rather than inheriting it transitively fromjackson-databind.codec-autoconfiguredeclaresspring-contextandspring-beans, which it compiles against, plus the four engine jars it references for@ConditionalOnClassas optional dependencies. It previously relied on transitives of optional dependencies.protobuf.versionmoved to the parent pom alongside every other version property.
Added
- CI-only dependency hygiene gates in the
ciprofile:dependency:analyze-onlywithfailOnWarning, andmaven-enforcer-pluginwithdependencyConvergence,requireUpperBoundDeps,banDuplicatePomDependencyVersions, andbanDynamicVersions. The default build is unaffected, so contributors do not hit these locally.
Requirements
- Spring Boot 4.1.1 (from 4.0.5)
- protobuf-java 4.36.0 (from 4.35.0)
Documentation
- MkDocs Material documentation site deployed to GitHub Pages, linked from the README.
0.2.0
Breaking changes
-
Spring Boot auto-configuration moved out of the backend modules into the new
codec-autoconfiguremodule. Spring Boot applications should now depend on
codec-spring-boot-starterplus a backend module; the backend modules
themselves are Spring-free and no longer register anything on their own.Migration: add
codec-spring-boot-starteralongside your existing backend
dependency. Non-Spring users are unaffected. -
Backend codec implementation classes (
JacksonCodec,GsonCodec,
ProtobufCodec) are now package-private. Interact with codecs through the
Codec<T>interface returned by the factories. -
Removed the empty
*CodecPropertiesconfiguration records (codec.jackson,
codec.gson,codec.protobufprefixes had no properties).
Added
- Codec composition:
Codec.andThen(Codec<byte[]>)layers byte-level
transforms (compression, encryption, ...) onto any codec, unwinding them
automatically in reverse order on decode CompressionStreamCodecbase class for stream-based compression transforms,
with built-in protection against decompression bombs (64 MiB default cap,
tunable per instance)GzipCodec(gzip/RFC 1952 framing) andDeflateCodec(zlib/RFC 1950
framing with configurable compression level) built-in transforms- Jackson 2.x backend (
codec-jackson2) for projects still on
com.fasterxml.jackson codec-autoconfiguremodule with deterministic backend precedence when
multiple backends are present (Jackson 3 → Jackson 2 → Gson → Protobuf);
a user-definedCodecFactorybean always winscodec-spring-boot-starterbundlingcodec-coreandcodec-autoconfigureAutomatic-Module-Namemanifest entries (org.jwcarman.codec.*) in all jars
Changed
- The parent pom no longer inherits
spring-boot-starter-parent; Spring Boot
versions are managed via aspring-boot-dependenciesBOM import and build
plugin versions are pinned explicitly
Documentation
- New documentation site: https://jwcarman.github.io/codec/
- Javadoc across the public API
0.1.0
Codec 0.1.0
Initial release of Codec — a type-safe serialization abstraction for Java.
Highlights
- Core SPI —
Codec<T>,CodecFactory, andTypeRef<T>super type token with properequals()/hashCode()for use as cache keys - Jackson backend — JSON serialization via
ObjectMapper, supports generic types throughTypeRef - Gson backend — JSON serialization via
Gson, supports generic types throughTypeRef - Protocol Buffers backend — Binary serialization using idiomatic
Parser<T>API (Protobuf 4.x) - Spring Boot auto-configuration — Drop a backend on the classpath and get a
CodecFactorybean automatically - BOM module —
codec-bomfor dependency version alignment
Requirements
- Java 25+
- Spring Boot 4.x
Getting Started
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jwcarman.codec</groupId>
<artifactId>codec-bom</artifactId>
<version>0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.jwcarman.codec</groupId>
<artifactId>codec-jackson</artifactId>
</dependency>@Service
public class MyService {
private final Codec<Person> codec;
public MyService(CodecFactory factory) {
this.codec = factory.create(Person.class);
}
}See the README for full documentation.