Announcing j4rs

j4rs stands for “Java for Rust” and allows effortless calls to Java code, from Rust.


Some time ago, on a need to call Java code from Rust, I started the j4rs project. The main idea was to implement a crate that would give the ability to its users to make calls to Java easily, so that they can benefit from the huge Java ecosystem.

By “easily”, I mean:

  • Taking care about the configuration required by JNI (e.g. jvm shared native library inclusion/linking).
  • Creating an intuitive, simple API to make Java calls (Rust -> Java direction).
  • Allowing Java -> Rust callbacks.
  • Seamlessly using the crate on Linux or Windows machines (provided that Java is installed, of course).
  • Following a “Rust-first” approach: Rust code should create and manage the JVM rather the other way round.

Along the way, I found out there were other crates that could be used for calling Java code, but it seemed that the programmer’s intervention with the “JNI specifics” could be minimized.

Many of us, rust programmers, know how to write Java, but chances are that not so many know (or are willing to deal with) the peculiarities and pitfalls of JNI.

As always, comfort comes with some sacrifices. Hiding the JNI specifics means using reflection and possibly objects serialization. This brings some performance penalty.

j4rs is for those of us who are willing to pay this price.

Quickstart

Just define the j4rs dependency in the Cargo.toml:

[dependencies]
j4rs = "0.6"

Here is a simple example where we create an empty Java String and we invoke the String’s isEmpty() method. Of course, the prerequisite is that Java is installed in the system.

use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};

// Create a JVM
let jvm = JvmBuilder::new().build().unwrap();

// Create a java.lang.String instance
let string_instance = jvm.create_instance(
    "java.lang.String",	// The Java class to create an instance for
    &[]			// The InvocationArgs to use for the constructor call - empty for this example
).unwrap();

// The instances returned from invocations and instantiations
// can be viewed as pointers to Java Objects.
// They can be used for further Java calls.
// For example, the following invokes the isEmpty
// method of the created java.lang.String instance
let boolean_instance = jvm.invoke(
    &string_instance,	// The String instance created above
    "isEmpty",		// The method of the String instance to invoke
    &[],		// The InvocationArgs to use for the invocation - empty for this example
).unwrap();

// If we need to transform an Instance to Rust value,
// we use the to_rust method
let rust_boolean: bool = jvm.to_rust(boolean_instance).unwrap();
println!("The isEmpty() method of the java.lang.String instance returned {}", rust_boolean);
// The above prints:
// The isEmpty() method of the java.lang.String instance returned true

Download Maven artifacts

JRE-included classes are not enough. We always need to call other Java libraries in order to implement something useful. The Maven Central contains all the libraries that we would ever need.

With j4rs, we can easily download maven artifacts in order to use them in our Rust application. Here is an example how we could deploy the Apache commons-lang:

// Create a JVM
let jvm = JvmBuilder::new().build().unwrap();

// Define a Maven Artifact
let artifact = MavenArtifact::from("org.apache.commons:commons-lang3:3.9");

// Deploy the artifact in order to call it from our Rust code
jvm.deploy_maven(artifact).unwrap();

Edit: A comment in Reddit indicated that deploy is misleading here because the word may refer to Maven’s deploy phase. This is totally correct and therefore, the deploy_maven method will be deprecated and will be renamed.

A good practice is that the deployment of maven artifacts is done by build scripts, during the crate’s compilation. This ensures that the classpath is properly populated during the actual Rust code execution.

Note: Currently, the Maven deployment does not take into consideration transitive dependencies. Hopefully, this will be supported in the future.

Use the Maven artifact

Let’s now use the aforementioned maven artifact in order to print the user’s $HOME directory:

let jvm = JvmBuilder::new().build().unwrap();

// Equivalent Java code: SystemUtils system_utils = new SystemUtils();
let system_utils = jvm.create_instance(
    "org.apache.commons.lang3.SystemUtils",
    &[]
).unwrap();

// Equivalent Java code:
// system_utils.getUserHome().getAbsolutePath();
let home_path: String = jvm.chain(system_utils)
    .invoke("getUserHome", &[]).unwrap()
    .invoke("getAbsolutePath", &[]).unwrap()
    .to_rust().unwrap();

println!("{}", home_path);

Next Steps

In the future, I plan to evolve j4rs with supporting modules, using protocol buffers for faster serialization and implementing macros for a more “Java-ish” programming feel.

I hope you find this crate useful.


Thanks for reading!