Repository navigation
feat(bindings): Java binding generated from the UDL - #582
Merged
Merged
Conversation
…ndgen-java 267 files in com.offlineprotocol.sdk: the engine (OfflineProtocol), every record and enum of the interface definition, the callback interfaces a host implements (EventCallback, the transport callbacks, the storage providers), MeshServices and DataStore. It is the same API surface Swift, Kotlin and Python expose, typed, and talks to the library through the Java 22 foreign function API, with no JNA and no native glue. uniffi.toml sets the package and the library name. Written by scripts/generate-bindings.sh, which the next commit teaches to run the Java generator; the output here is what it produces.
scripts/generate-bindings.sh runs uniffi-bindgen-java after Swift, Kotlin and Python, so the four are one artifact set (C1) and no caller can produce a partial one. The Java generator is versioned apart from uniffi and tracks it by minor, so the script refuses any series but 0.5, before writing anything, for the reason it refuses a mismatched uniffi-bindgen: the Java checks the library's FFI contract and fails at the first call. The guard script runs the generator against stub bindgens as before and now asserts four calls, the Java output path, the Java series refusal, and that the scan for direct bindgen invocations also catches uniffi-bindgen-java. CI deletes and diffs the Java output with the rest, and every job that runs the generator, the release workflow's three included, installs the Java generator. The scripts that check for uniffi-bindgen up front check for it too.
Compiles bindings/java/src/main/java with release 22, the first Java in which the foreign function API the generated code calls is final, and builds the binary, sources and Javadoc jars. There is no dependency: no JNA, no native glue. The tests take the native library from -PnativeLibrary (default: this checkout's Cargo debug output) through the generated loader's libraryOverride property, and run with --enable-native-access.
The counterpart of bindings/c/consumer-check/main.c, against the generated API: an instance-less function, an engine whose events reach the callback on the calling thread and the poll queue, a refusal as a typed ProtocolException subclass, a Java provider passing the storage conformance suite and giving the engine an address, the built-in file stores, and a provider that cannot store failing the suite. The generated code checks the library's FFI checksums before the first call, so a binding generated from a different definition than the library fails all of it. The generated ProtocolConfig has no defaults, so the test spells out the definition's.
…macOS Builds the library, then compiles the committed generated sources and runs the consumer check against it on JDK 25 with Gradle 9.3.1. The generated code checks the library's FFI checksums at load, so the job also says the two were generated from the same definition.
…in the docs bindings/java/README.md is the guide: what to install and how the JVM finds the library, a quick start, events, transports and storage, and the build. docs/bridges/java.md is the contract: which shared rules reach a binding with no hand-written code, why the generator is pinned to a series, that the generated records have no defaults and so a Java caller can disagree with the other bindings about one, that the host fills the telemetry platform, and what the JVM requires (JDK 22, native access). The binding gets its row wherever the others have one, and every place that said the artifact set was three bindings, or listed what to install to regenerate it, now says four.
The Gradle project and the docs claim desktop JVMs, and the C binding already has a Windows job. The generated loader's Windows path was the one unexercised.
… version Only the series was checked at run time, so a moved pin that missed the CI, release, or documentation lines still passed. The exact release is now one variable in the generator, and a guard compares every written install, and the release workflow's variable, against it.
Asserting only that the report was not all-pass would also pass if the exception had crashed the engine. The report must carry the storage error it was converted to.
The bundle and upload scripts named the Android library's coordinate and its aar. MAVEN_ARTIFACT_ID and MAVEN_PACKAGING select the Java binding's jar, defaulting to the Android library so its release is unchanged. The tests bundle a Java repository, refuse the Android one as Java, and pin that the re-run probe asks for the Java POM, not the Android one.
com.offlineprotocol:offline-protocol-java, with the POM the Android library carries and the license notices under META-INF. CI writes the repository on every pull request, so a publication error shows there and not on a tag.
Built and tested against the release profile's library in a credential free job, then signed and uploaded as its own deployment under the Android library's switch, so one refusal does not hold back the other. The release waits on its tests. Documented as J6, with install steps.
bahdotsh
approved these changes
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Java binding, generated directly from the UDL by
uniffi-bindgen-java0.5.2 (the community generator for UniFFI 0.32). It is not a wrapper over the C API: it sits beside the Swift, Kotlin and Python bindings and is regenerated with them byscripts/generate-bindings.sh.bindings/java/src/main/java(packagecom.offlineprotocol.sdk), using the JDK foreign function API (JDK 22 or later).scripts/generate-bindings.shgenerates Java with the other three, and its guard tests cover it (61 guards pass).bindings/java) and a consumer check that loads the real library, with an in-memory MLS and protocol-state storage (7 tests).Java BindingCI job on Linux and macOS, and the install ofuniffi-bindgen-javain the Python-bindings drift gate and inrelease.yml(all installs use--force, as in chore(uniffi): bump UniFFI from 0.30 to 0.32 #578).docs/bridges/java.mdrecords the contract, and the bridge docs, README and CONTRIBUTING name it.Related issues
Builds on #578 (the UniFFI 0.30 to 0.32 bump, which the Java generator needs).
Type of change
feat: new featureChecks run locally
./scripts/generate-bindings.sh: no drift on the rebased branch.scripts/tests/test-generate-bindings.sh: 61 guards pass.gradle buildinbindings/java(JDK 26, Gradle 9.3.1): the 7 consumer-check tests pass against the freshly built library.Not run locally: the macOS CI job,
release.ymlchanges, and the React Native and Swift builds (this PR does not change their sources, only the shared generate script).