Java Foreign Function & Memory (FFM) bindings for Zstandard.
No JNI, no hand-written C. Targets JDK 25+ (stable java.lang.foreign).
The differentiator is dictionary compression — trained from your own data via
ZDICT — plus a zero-copy MemorySegment API for callers whose bytes are
already off-heap (e.g. an mmap slice in, an arena buffer out).
Multi-module Maven build (io.github.dfa1.zstd:zstd):
zstd/— the library module, artifactIdzstd, pure-Java FFM bindings (packageio.github.dfa1.zstd). The only module with Java sources.native/<classifier>/— one module per platform; each packages alibzstd.{dylib,so,dll}built from thethird_party/zstdsubmodule. No Java. Classifiers:osx-aarch64,osx-x86_64,linux-x86_64,linux-aarch64,windows-x86_64,windows-aarch64.bom/— dependency BOM.zstd/— vendoredfacebook/zstdgit submodule (the C source of truth).
scripts/build-zstd.sh <output-resources-dir> <classifier> compiles the zstd
library sources directly with zig cc — no zstd Makefile, no CMake. Zig
bundles clang + libc for every target, so any host cross-compiles any of the six
classifiers hermetically. The Maven exec plugin runs it in generate-resources;
it is idempotent (skips if the library already exists).
Key flags: -DZSTD_DISABLE_ASM=1 (drops x86-only .S, identical codegen on every
target), -fvisibility=hidden on ELF/Mach-O, and lld --export-all-symbols on
Windows (do not use hidden visibility there — it suppresses the PE exports).
Built .dylib/.so/.dll are git-ignored; they are regenerated from the submodule.
- Checkstyle-clean (
./mvnw validateruns it); see the Code style section below. - Native pointers wrap in
NativeObject(AutoCloseable, idempotent close). - All native handles live in
Bindings;size_t/unsigned long longmap toJAVA_LONG. Public methods guard zstd's negative sentinels. - Native structs: model the layout as a named
StructLayoutand access fields throughstatic final VarHandles from it (LAYOUT.varHandle(groupElement("…"))), deriving size fromLAYOUT.byteSize(). No hardcoded offsets/sizes — seeZstdStreamBuffer. - API is segment-first for the zero-copy fast path, with thin
byte[]overloads for heap callers. Never allocate abyte[]for decode output on a hot path — see docs/zero-copy.md. - Run with
--enable-native-access=ALL-UNNAMED.
- Cover happy path, negative cases (invalid input / errors), and corners (empty, zero, max, boundaries). Unit tests must be fast — no file I/O, network, or sleep; mock or use in-memory data.
- Integration tests are ground truth (no formal spec): interop with the
zstd-jnireference binding (luben, the zstd C library via JNI) and the vendored golden corpus underthird_party/zstd/tests/(golden-compression,golden-decompression,golden-decompression-errors,golden-dictionaries). Write one for every encoding round-trip and file-format boundary. - JUnit 5 + Mockito (BDDMockito) + AssertJ. Class under test named
sut. Every test has// Given/// When/// Then. BDDMockito only:given(mock.m()).willReturn(v)/then(...)(static-import onlygiven/then, neverwillReturn/willThrow). For exception assertions, capture the action under// Whenas aThrowingCallable result = () -> sut.m(...);and assert it under// ThenwithassertThatThrownBy(result)— the callable is the When, the assertion is the Then. - Prefer
@ParameterizedTestover copy-paste (@ValueSource, else@ArgumentsSource/named cases). For large input spaces use seeded-random@MethodSourcegenerators — they find corners examples miss. Put generators inRandomArrays(integration) or a similar util; keep counts low (10–30) when the test does file I/O or JNI. @Nestedgroups related scenarios (@BeforeEachin a nested class applies only to it). Private helpers go after all@Testmethods.
- 4-space indent, zero SonarQube bugs/smells, no
sun.misc.Unsafeor internal JDK APIs. - Prefer explicit over clever; fail fast on unhandled cases.
- Always braces for
if/else/for/while, even one-liners (if (c) { return a; }). - Time quantities use
java.time.Duration, neverlong(nolong timeoutMs/delayNanos). Exception: low-level JDK interop takinglong ns(Thread.sleep,LockSupport.parkNanos,System.nanoTimemath) — convert at the call site viaduration.toNanos()/toMillis().
- Every public method: main prose description,
@paramper parameter,@return(unlessvoid). Every public record:@paramper component on the class doc.@see-only counts as no description. - All
///Markdown — no HTML (checkstyleRegexpSinglelineblocks<p>,<ul>,<li>,<strong>,<pre>,<table>, …). Use blank///for paragraphs,-lists,```java ```,**bold**. Cross-refs[ClassName#method(ParamType)]— verify the target exists (wrong refs are errors). - American English everywhere (javadoc, comments, identifiers):
recognize/optimize/finalize/serialize/behavior/color, never-ise/-isation/-our. Matches the JDK (Object.finalize,Serializable) and zstd's C docs. - Check:
./mvnw javadoc:javadoc -pl coremust produce zero output.