Skip to main content
From v0.30.0 the SDK has a typed Java API for the JVM on desktop and server. It is generated from the same interface definition as the Swift, Kotlin and Python bindings and calls the native library through the Java foreign function API, so it needs no JNA, no JNI and no native glue of its own. On Android, use the Android library instead. This binding is for any other JVM.

Install

The jar is on Maven Central as com.offlineprotocol:offline-protocol-java:
The jar holds the generated Java and no native code. Download the native library for your platform from the v0.30.0 GitHub release: Use a jar and a native library from the same release. Run on JDK 22 or later, allow native access, and point the JVM at the library:
To name the file by absolute path instead, pass -Duniffi.component.offline_protocol.libraryOverride=/path/to/liboffline_protocol_uniffi.dylib. Without --enable-native-access the JVM warns on the first native call, and a future JDK refuses it.

Quick start

The package is com.offlineprotocol.sdk. OfflineProtocol is the engine, and MeshServices and DataStore hang off it. Every record and enum of the definition is a class. An engine refusal is a subclass of ProtocolException named for the variant; see error codes. OfflineProtocolLib holds the functions that need no engine, such as deriveAddress, parseInvite and runStorageConformance. Java has no default arguments, so new ProtocolConfig(...) takes every field in order. When a release changes a default in the interface definition, your Java code keeps passing the old value until you change it. The binding’s ConsumerCheckTest.config() carries the definition’s defaults field for field.

Events

setEventCallback installs an EventCallback, or pollEvent takes the oldest queued event. Every event is queued either way. An event is the engine’s JSON text, so parse it with the library you already use; the payloads are the ones in the event reference. The callback runs on whichever thread is inside the engine, before the engine call that produced it returns. It must be safe from any thread, must not call back into the engine (closing the engine included) and must not throw.

What your host provides

Your process is the platform, so it owns the radios and sockets:
  • Transports. Implement BleTransportCallback, WifiDirectTransportCallback, ReticulumTransportCallback and NostrTransportCallback to learn when a queue has frames, and drain them with the ble*, wifiDirect*, internet*, reticulum* and nostr* methods.
  • Storage. Implement MlsStorageProvider (key material, backed by a keystore) and ProtocolStateStorageProvider and pass both to initializeMls, or use the built-in file stores as above. Prove an adapter with OfflineProtocolLib.runStorageConformance.
  • Telemetry platform. If you enable telemetry, pass TelemetryConfig.os and osMajor as the platform the process actually runs on (TelemetryOs.LINUX, MACOS or WINDOWS), read from the JVM’s os.name and os.version. No layer in this binding fills them.
  • Lifecycle. start, stop and process on a timer. try-with-resources frees the engine once; it does not wait for calls in flight on other threads.

Reference

The SDK is AGPL-3.0-only with a commercial license available; see licensing.