Two devices, Alice and Bob, in one JVM: they pair, establish an end-to-end encrypted session, use conversation signals, transfer a file, and create an MLS group, using the Java binding.
alice off1q89jrev...
bob off1q9jxmhy...
ok: bob and alice are paired
ok: bob sees alice online
ok: bob sees alice typing
ok: bob received "hello bob" (encrypted end to end)
ok: alice received a read receipt
ok: alice received a threaded reply
ok: bob received the original file bytes
ok: bob joined the MLS group
ok: bob received the group message
alice transport [Internet]
group members [off1..., off1...]
done
- Creating an engine from a
ProtocolConfig(every field spelled out: the generated Java record has no defaults, see J2). - The engine's own encrypted file stores (
initializeMlsWithFileStores), so no storage provider is needed. - Pairing from events: the app reads
pollEvent(), and answersconnection_request_receivedandconnection_acceptedwith the key package exchange. - Presence and typing indicators, followed by a normal encrypted message, a read receipt tied to its message ID, and a high-priority rich reply threaded to the original message.
sendFilewith byte-for-byte verification of the reassembledfile_receivedevent. That event contains base64-encoded data for the host to save or use.- Creating an MLS group, inviting the paired peer, and sending a group message.
- Reading active transports and group membership back from the SDK.
- A transport's job, in a dozen lines: take what an engine wants to send
(
internetGetNextMessage) and hand it to the engine it is addressed to (internetMessageReceived), then acknowledge the successful carrier write (internetConfirmSent). A real app does this over a radio or a socket. Everything else here, the pairing, the MLS session and the encryption, is the engine's own.
The in-process carrier delivers frames directly between the two engines. It does not act as an internet relay or record transport metrics; the event checks show that the SDK processed each feature end to end.
You need JDK 22 or later and the native library.
cargo build -p offline-protocol-uniffi # from the repository root
cd examples/java-app
gradle run # Gradle 9; -PnativeLibrary=<file> for another librarysettings.gradle.kts includes bindings/java as a composite build and
substitutes it for the published coordinate, so the example runs against the
source in this checkout. In your own project, drop that block and depend on a
release from Maven Central:
dependencies {
implementation("com.offlineprotocol:offline-protocol-java:<version>")
}Run on JDK 22 or later with --enable-native-access=ALL-UNNAMED and the
library's path in -Duniffi.component.offline_protocol.libraryOverride=<file>
(or on java.library.path); build.gradle.kts does both for gradle run.