uniffi-bindgen-java
Generate UniFFI bindings for Java.
Official Kotlin bindings already exist, which can be used by any JVM language including Java. The Java specific bindings use Java-native types where possible for a more ergonomic interface, for example the Java bindings use CompletableFutures instead of kotlinx.coroutines.
We highly reccommend you use UniFFI's proc-macro definition instead of UDL where possible.
Requirements
- Java 20+:
javac, andjar - The Java Native Access JAR downloaded and its path added to your
$CLASSPATHenvironment variable.
Installation
MSRV is 1.87.0.
cargo install uniffi-bindgen-java --git https://github.com/IronCoreLabs/uniffi-bindgen-java
Usage
uniffi-bindgen-java --help
Java scaffolding and bindings generator for Rust
Usage:
Commands:
generate Generate Java bindings
scaffolding Generate Rust scaffolding code
print-repr Print a debug representation of the interface from a dynamic library
Options:
-h, --help Print help
-V, --version Print version
Generate Bindings
uniffi-bindgen-java generate --help
Generate Java bindings
Usage:
Arguments:
<SOURCE> Path to the UDL file or compiled library (.so, .dll, .dylib, or .a)
Options:
-o, --out-dir <OUT_DIR> Directory in which to write generated files. Default is same folder as .udl file
-n, --no-format Do not try to format the generated bindings
-c, --config <CONFIG> Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence
--crate <CRATE_NAME> When a library is passed as SOURCE, only generate bindings for this crate. When a UDL file is passed, use this as the crate name instead of attempting to locate and parse Cargo.toml
--metadata-no-deps Whether we should exclude dependencies when running "cargo metadata". This will mean external types may not be resolved if they are implemented in crates outside of this workspace. This can be used in environments when all types are in the namespace and fetching all sub-dependencies causes obscure platform specific problems
-h, --help Print help
-V, --version Print version
As an example:
> git clone https://github.com/mozilla/uniffi-rs.git
> cd uniffi-rs/examples/arithmetic-proc-macro
> cargo b --release
> uniffi-bindgen-java generate --out-dir ./generated-java ../../target/release/libarithmeticpm.so
> ll generated-java/uniffi/arithmeticpm/
total 216
-rw-r--r-- 1 user users 295 Jul 24 13:02 ArithmeticExceptionErrorHandler.java
-rw-r--r-- 1 user users 731 Jul 24 13:02 ArithmeticException.java
-rw-r--r-- 1 user users 3126 Jul 24 13:02 Arithmeticpm.java
-rw-r--r-- 1 user users 512 Jul 24 13:02 AutoCloseableHelper.java
-rw-r--r-- 1 user users 584 Jul 24 13:02 FfiConverterBoolean.java
...
> cat generated-java/uniffi/arithmeticpm/Arithmeticpm.java
package uniffi.arithmeticpm;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class Arithmeticpm {
public static long add(long a, long b) throws ArithmeticException {
try {
...
Generate Scaffolding
uniffi-bindgen-java scaffolding --help
Generate Rust scaffolding code
Usage:
Arguments:
<UDL_FILE> Path to the UDL file
Options:
-o, --out-dir <OUT_DIR> Directory in which to write generated files. Default is same folder as .udl file
-n, --no-format Do not try to format the generated bindings
Print Debug Representation
uniffi-bindgen-java print-repr --help
Print a debug representation of the interface from a dynamic library
Usage:
Arguments:
<PATH> Path to the library file (.so, .dll, .dylib, or .a)
Integrating Bindings
After generation you'll have an --out-dir full of Java files. Package those into a .jar using your build tools of choice, and the result can be imported and used as per normal in any Java project with the JNA dependency available.
Any top level functions in the Rust library will be static methods in a class named after the crate.
Configuration
The generated Java can be configured using a uniffi.toml configuration file.
| Configuration name | Default | Description |
|---|---|---|
package_name |
uniffi |
The Java package name - ie, the value use in the package statement at the top of generated files. |
cdylib_name |
uniffi_{namespace} |
The name of the compiled Rust library containing the FFI implementation (not needed when using generate --library) |
generate_immutable_records |
false |
Whether to generate records with immutable fields (record instead of class). |
custom_types |
A map which controls how custom types are exposed to Java. See the custom types section of the UniFFI manual | |
external_packages |
A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Java package which will be used referring to types in that crate. See the external types section of the manual | |
rename |
A map to rename types, functions, methods, and their members in the generated Java bindings. See the renaming section. | |
android |
false |
Used to toggle on Android specific optimizations (warning: not well tested yet) |
android_cleaner |
android |
Use the android.system.SystemCleaner instead of java.lang.ref.Cleaner. Fallback in both instances is the one shipped with JNA. |
omit_checksums |
false |
Whether to omit checking the library checksums as the library is initialized. Changing this will shoot yourself in the foot if you mixup your build pipeline in any way, but might speed up initialization. |
Example
Custom types
[bindings.java]
package_name = "customtypes"
[bindings.java.custom_types.Url]
# Name of the type in the Java code
type_name = "URL"
# Classes that need to be imported
imports = ["java.net.URI", "java.net.URL"]
# Functions to convert between strings and URLs
lift = "new URI({}).toURL()"
lower = "{}.toString()"
External Types
[bindings.java.external_packages]
# This specifies that external types from the crate `rust-crate-name` will be referred by by the package `"java.package.name`.
rust-crate-name = "java.package.name"
Notes
- failures in CompletableFutures will cause them to
completeExceptionally. The error that caused the failure can be checked withe.getCause(). When implementing an async Rust trait in Java, you'll need tocompleteExceptionallyinstead of throwing. SeeTestFixtureFutures.javafor an example trait implementation with errors. - all primitives are signed in Java by default. Rust correctly interprets the a signed primitive value from Java as unsigned when told to. Callers of Uniffi functions need to be aware when making comparisons (
compareUnsigned) or printing when a value is actually unsigned to code around footguns on this side. - this is an internal note for development but because Enum variants are not cases/hanging off their parent in Java, their named standalone, they can conflict with any/all
java.langtypes. We could do extensive checking and forced renaming around this, but instead we use fully qualified names for alljava.langtypes in all templates. Ensure that when you're making changes you're not dropping those qualified names or adding generated code without them.
Unsupported features
- Defaults aren't supported in Java so uniffi struct, method, and function defaults don't exist in the Java code. Note: a reasonable case could be made for supporting defaults on structs by way of generated builder patterns. PRs welcome.
- Output formatting isn't currently supported because a standalone command line Java formatter wasn't found. PRs welcome enabling that feature, the infrastructure is in place.
Testing
We pull down the pinned examples directly from Uniffi (currently v0.31.0) and run Java tests using the generated bindings. Run cargo t to run all of them.
Note that if you need additional toml entries for your test, you can put a uniffi-extras.toml as a sibling of the test and it will be read in addition to the base uniffi.toml for the example. See CustomTypes for an example. Settings in uniffi-extras.toml apply across all namespaces.
Versioning
uniffi-bindgen-java is versioned separately from uniffi-rs. We follow the Cargo SemVer rules, so versions are compatible if their left-most non-zero major/minor/patch component is the same. Any modification to the generator that causes a consumer of the generated code to need to make changes is considered breaking.
uniffi-bindgen-java is currently unstable and being developed by IronCore Labs to target features required by ironcore-alloy. The major version is currently 0, and most changes are likely to bump the minor version.
Compatibility
Keeping this testable requires fully pinned uniffi-rs versions. The version of uniffi-rs will always be called out in the changelog when it changes, so if you're stuck on a specific version due to other bindings, you can stay on a compatible version of these bindings.