Java wrapper around the Rust core via JNI.
Ingestion is asynchronous. ingestRecordOffset() (and ingestRecordsOffset()) return as
soon as the record/batch is queued — the SDK sends it and tracks its acknowledgment in the
background.
- Idiomatic flow: ingest in a loop, then call
flush()once (after a bounded batch, or periodically for a long-running stream) to confirm durability. Equivalently, callwaitForOffset()on the last returned offset — acks are ordered, so confirming the last offset confirms every prior record. - Async monitoring: register an
AckCallback(onAck/onError) to track progress without blocking the ingest loop. - Per-record
waitForOffset(): use when a specific record must be confirmed before continuing. Avoid calling it after every record in a tight loop, since that limits throughput to one record per round-trip. - New code should use
ZerobusProtoStream/ZerobusJsonStream(offset-based) rather than the deprecatedZerobusStream. With the deprecated API, keep the last future and.join()once after the loop rather than joining per record (the latter is discouraged for the same throughput reason).
java/
├── src/main/java/com/databricks/zerobus/
│ ├── ZerobusSdk.java # Main SDK class
│ ├── ZerobusStream.java # Legacy generic stream (deprecated)
│ ├── ZerobusProtoStream.java # Proto ingestion stream
│ ├── ZerobusJsonStream.java # JSON ingestion stream
│ ├── ZerobusArrowStream.java # Arrow Flight stream (Beta)
│ ├── NativeLoader.java # Platform-specific JNI library loading
│ └── ... # Config, exceptions, callbacks
├── src/test/ # JUnit 5 + Mockito tests
├── pom.xml # Maven config
└── tools/ # Schema generation
The JNI native library is built from rust/jni/. It's packaged inside the JAR for all supported platforms.
Run from java/:
mvn compile— Compilemvn test— Run testsmvn test -Dtest=ClassName#method— Run specific testmvn clean package— Build JAR (includes fat JAR)mvn spotless:check— Check formatting (Google Java Format)mvn spotless:apply— Auto-format
JNI is the most manual of all the bindings. Key considerations:
- Handle pattern: Java holds a
long nativeHandlefield — an opaque pointer to the Rust object. All native methods pass this handle. If the handle is invalid (use-after-close), Rust panics or returns an error. - No finalizers: Unlike Go/Python/TypeScript, Java streams do not have GC-triggered cleanup. Users must call
close()explicitly or use try-with-resources. Forgetting this leaks native memory. - AutoCloseable: SDK and stream classes implement
AutoCloseable. Always prefer try-with-resources. - Async bridge:
CompletableFuturebridges Rust futures to Java. Stream creation and acknowledgment waiting return futures. - Thread safety: The Java SDK is not thread-safe. Do not share SDK or stream instances across threads without external synchronization.
- Native library loading (
NativeLoader.java): Detects OS + arch, extracts the correct.so/.dylib/.dllfrom the JAR classpath to a temp directory, loads viaSystem.load(). MarkeddeleteOnExit().
Public API is everything in com.databricks.zerobus with public visibility:
- Removing or renaming public classes, methods, or fields is breaking.
- Changing method signatures (parameter types, return types, checked exceptions) is breaking.
- Deprecate with
@Deprecatedannotation and Javadoc explaining the replacement. - JNI native method signatures (
private static native ...) are internal but must stay in sync withrust/jni/— changing one without the other causesUnsatisfiedLinkErrorat runtime.
- JNI calls have non-trivial overhead (~100ns per crossing). Batch APIs reduce crossings.
- Proto descriptors are passed as
byte[]— copied at the boundary. This is a one-time cost at stream creation. - Record payloads (
byte[]for proto,Stringfor JSON) are copied across JNI. For high-throughput, prefer proto with batch ingestion. - Native library extraction happens once at class load time — first-use latency includes temp file I/O.
- Every PR must update
java/NEXT_CHANGELOG.mdunder the appropriate section if it changes user-facing behavior. - Update
java/README.mdif the change affects usage, setup, or API surface. - Add or update examples in
java/examples/for new or modified APIs. - Add Javadoc for all new public classes/methods.
- Version source:
java/pom.xml(<version>x.y.z</version>). - Tag:
java/v<semver>→ triggersrelease-java.yml→ builds JNI native libs for all platforms, packages into JAR → publishes to Maven Central. - The JAR bundles native libraries for all 5 platforms. The JNI release depends on
rust/jni/— if Rust JNI code changed, both must be coordinated. - On version bump PR: update version in
pom.xml, moveNEXT_CHANGELOG.mdcontents toCHANGELOG.md, resetNEXT_CHANGELOG.md.
- Java 8+ compatibility (source/target 1.8)
- Google Java Format via Spotless
- Arrow version: 17.0.0