MCPcopy Index your code
hub / github.com/astonbitecode/j4rs

github.com/astonbitecode/j4rs @v0.25.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.25.2 ↗ · + Follow
894 symbols 2,698 edges 98 files 139 documented · 16%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

j4rs

crates.io Maven Central Build

j4rs stands for 'Java for Rust' and allows effortless calls to Java code from Rust and vice-versa.

Features

Instead of donating to the dev

With the hope that you find this project useful, please consider donating to charity.

Usage

Basics

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

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

// Create a java.lang.String instance
let string_instance = jvm.create_instance(
    "java.lang.String",     // The Java class to create an instance for
    InvocationArg::empty(), // An array of `InvocationArg`s to use for the constructor call - empty for this example
)?;

// 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
  InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// If we need to transform an `Instance` to some Rust value, the `to_rust` should be called
let rust_boolean: bool = jvm.to_rust(boolean_instance)?;
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

// Static invocation
let _static_invocation_result = jvm.invoke_static(
  "java.lang.System",     // The Java class to invoke
  "currentTimeMillis",    // The static method of the Java class to invoke
  InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// Access a field of a class
let system_class = jvm.static_class("java.lang.System")?;
let system_out_field = jvm.field(&system_class, "out");

// Retrieve an enum constant using the field
let access_mode_enum = jvm.static_class("java.nio.file.AccessMode")?;
let access_mode_write = jvm.field(&access_mode_enum, "WRITE")?;

// Retrieve a nested class (note the use of `$` instead of `.`)
let state = jvm.static_class("java.lang.Thread$State")?;

Instancess of Java Lists and Maps can be created with the java_list and java_map functions:

let rust_vec = vec!["arg1", "arg2", "arg3", "arg33"];

// Generate a Java List<String>. The Java List implementation is the one that is returned by java.util.Arrays#asList
let java_list_instance = jvm.java_list(
    JavaClass::String,
    rust_vec)?;

let rust_map = HashMap::from([
    ("Potatoes", 3),
    ("Tomatoes", 33),
    ("Carrotoes", 333),
]);

// Generate a java.util.HashMap.
let java_map_instance = jvm.java_map(
    JavaClass::String,
    JavaClass::Integer,
    rust_map)?;

Passing arguments from Rust to Java

j4rs uses the InvocationArg enum to pass arguments to the Java world.

Users can benefit of the existing TryFrom implementations for several basic types:

let i1 = InvocationArg::try_from("a str")?;      // Creates an arg of java.lang.String
let my_string = "a string".to_owned();
let i2 = InvocationArg::try_from(my_string)?;    // Creates an arg of java.lang.String
let i3 = InvocationArg::try_from(true)?;         // Creates an arg of java.lang.Boolean
let i4 = InvocationArg::try_from(1_i8)?;         // Creates an arg of java.lang.Byte
let i5 = InvocationArg::try_from('c')?;          // Creates an arg of java.lang.Character
let i6 = InvocationArg::try_from(1_i16)?;        // Creates an arg of java.lang.Short
let i7 = InvocationArg::try_from(1_i64)?;        // Creates an arg of java.lang.Long
let i8 = InvocationArg::try_from(0.1_f32)?;      // Creates an arg of java.lang.Float
let i9 = InvocationArg::try_from(0.1_f64)?;      // Creates an arg of java.lang.Double

And for Vecs:

let my_vec: Vec<String> = vec![
    "abc".to_owned(),
    "def".to_owned(),
    "ghi".to_owned()];

// Creates List<String>
let i10 = InvocationArg::try_from(my_vec.as_slice())?;

let another_vec: Vec<f64> = vec![0.0, 1.0, 3.6];

// Creates List<Float>
let i11 = InvocationArg::try_from(another_vec.as_slice())?;

The j4rs apis accept InvocationArgs either as references, or values:

let inv_args = InvocationArg::try_from("arg from Rust")?;
let _ = jvm.create_instance("java.lang.String", &[&inv_args])?; // Pass a reference
let _ = jvm.create_instance("java.lang.String", &[inv_args])?;  // Move

The Instances returned by j4rs can be transformed to InvocationArgs and be further used for invoking methods as well:

let one_more_string_instance = jvm.create_instance(
  "java.lang.String",     // The Java class to create an instance for
  InvocationArg::empty(), // The `InvocationArg`s to use for the constructor call - empty for this example
)?;

let i11 = InvocationArg::try_from(one_more_string_instance)?;

To create an InvocationArg that represents a null Java value, use the From implementation with the Null struct:

let null_string = InvocationArg::from(Null::String);                // A null String
let null_integer = InvocationArg::from(Null::Integer);              // A null Integer
let null_obj = InvocationArg::from(Null::Of("java.util.List"));     // A null object of any other class. E.g. List

Passing custom arguments from Rust to Java

Custom types, for which there is no TryFrom implementation, are also supported via serialization.

To use a custom struct MyBean as an InvocationArg it needs to be serializable:

#[derive(Serialize, Deserialize, Debug)]
#[allow(non_snake_case)]
struct MyBean {
    someString: String,
    someInteger: isize,
}

Then, an InvocationArg can be created like:

let my_bean = MyBean {
    someString: "My String In A Bean".to_string(),
    someInteger: 33,
};
let ia = InvocationArg::new(&my_bean, "org.astonbitecode.j4rs.tests.MyBean");

And it can be used as an argument to a Java method that accepts org.astonbitecode.j4rs.tests.MyBean instances.

Of course, there should exist a respective Java class in the classpath for the deserialization to work and the custom Java Object to be created:

package org.astonbitecode.j4rs.tests;

public class MyBean {
    private String someString;
    private Integer someInteger;

    public MyBean() {
    }

    public String getSomeString() {
        return someString;
    }

    public void setSomeString(String someString) {
        this.someString = someString;
    }

    public Integer getSomeInteger() {
        return someInteger;
    }

    public void setSomeInteger(Integer someInteger) {
        this.someInteger = someInteger;
    }
}

Async support

(v0.16.0 onwards)

j4rs supports .async/.await viaJvm::invoke_async function. The function returns a Future, which is completed via the Receiver of a oneshot channel.

In Java side, the methods that can be invoked by invoke_async, must return a Java Future. When the Java Future completes, the Java side of j4rs invokes native Rust code that completes the pending Rust Future with either success or failure, using the Sender of the oneshot channel that was created when the invoke_async was called.

For example, assuming we have a Java method that returns a Future:

package org.astonbitecode.j4rs.tests;

public class MyTest {
  private static ExecutorService executor = Executors.newSingleThreadExecutor();

  // Just return the passed String in a Future
  public Future<String> getStringWithFuture(String string) {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    executor.submit(() -> {
      completableFuture.complete(string);
      return null;
    });
    return completableFuture;
  }
}

We can invoke it like following:

let s_test = "j4rs_rust";
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = jvm.invoke_async(&my_test, "getStringWithFuture", &[InvocationArg::try_from(s_test)?]).await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);

Please note that it is better for the Java methods that are invoked by the invoke_async function to return a CompletableFuture, as this improves performance.

j4rs handles simple Java Futures that are not CompletableFutures with polling, using an internal one-threaded ScheduledExecutorService.

This has apparent performance issues.

invoke_async and Send

Instances are Send and can be safely sent to other threads. However, because of Send Approximation, the Future returned by invoke_async is not Send, even if it just contains an Instance. This is because the Jvm is being captured by the async call as well and the Jvm is not Send.

In order to have a Future<Instance> that is Send, the Jvm::invoke_into_sendable_async can be used. This function does not get a Jvm as argument; it creates one internally when needed and applies some scoping workarounds in order to achieve returning a Future<Instance> which is also Send.

Discussion here.

Casting

An Instance may be casted to some other Class:

let instantiation_args = vec![InvocationArg::try_from("Hi")?];
let instance = jvm.create_instance("java.lang.String", instantiation_args.as_ref())?;
jvm.cast(&instance, "java.lang.Object")?;

Java arrays and variadics

// Create a Java array of Strings `String []`
let s1 = InvocationArg::try_from("string1")?;
let s2 = InvocationArg::try_from("string2")?;
let s3 = InvocationArg::try_from("string3")?;

let arr_instance = jvm.create_java_array("java.lang.String", &[s1, s2, s3])?;
// Invoke the Arrays.asList(...) and retrieve a java.util.List<String>
let list_instance = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(arr_instance)])?;

When creating an array of primitives, each InvocationArg item needs to be converted to one containing primitive value.

let doubles = vec![0.1, 466.5, 21.37];

// Creates Java `double` primitives within a Rust `Vec`
let double_args: Vec<InvocationArg> = doubles.iter()
    .map(|value| Ok::<_, J4RsError>(
        InvocationArg::try_from(value)?.into_primitive()?
    ))
    .collect::<Result<_, J4RsError>>()?

// Creates an instance of `double []`
let doubles_array = InvocationArg::try_from(
    jvm.create_java_array("double", &double_args)?
)?;

Java Generics

// Assuming the following map_instance is a Map<String, Integer>
// we may invoke its put method
jvm.invoke(&map_instance, "put", &[InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?;

Java primitives

Even if auto boxing and unboxing is in place, j4rs cannot invoke methods with primitive int arguments using Integer instances.

For example, the following code does not work:

let ia = InvocationArg::try_from(1_i32)?;
jvm.create_instance("java.lang.Integer", &[ia])?;

It throws an InstantiationException because the constructor of Integer takes a primitive int as an argument:

Exception in thread "main" org.astonbitecode.j4rs.errors.InstantiationException: Cannot create instance of java.lang.Integer at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:37) Caused by: java.lang.NoSuchMethodException: java.lang.Integer.(java.lang.Integer) at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getConstructor(Class.java:2151) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:69) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:34)

In situations like this, the java.lang.Integer instance should be transformed to a primitive int first:

```rust

Extension points exported contracts — how you extend this code

JsonValue (Interface)
Representation for a json value [7 implementers]
java/src/main/java/org/astonbitecode/j4rs/api/JsonValue.java
JavaArtifact (Interface)
Marker trait to be used for deploying artifacts. [3 implementers]
rust/src/provisioning.rs
DummyMapInterface (Interface)
(no doc) [2 implementers]
test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/DummyMapInterface.java
FxController (Interface)
(no doc) [2 implementers]
java-fx/src/main/java/org/astonbitecode/j4rs/api/jfx/controllers/FxController.java
ObjectValue (Interface)
Represents an object for j4rs needs [6 implementers]
java/src/main/java/org/astonbitecode/j4rs/api/ObjectValue.java
JavaFxSupport (Interface)
Provides JavaFx support. [1 implementers]
rust/src/jfx.rs
MavenDeployerApi (Interface)
(no doc) [4 implementers]
java/src/main/java/org/astonbitecode/j4rs/api/deploy/MavenDeployerApi.java
InstanceGeneratorDelegate (Interface)
Delegates the generation of Intsances, by creating Instances proxies. This is needed for example to execute code in the [2 …
java/src/main/java/org/astonbitecode/j4rs/api/services/delegates/InstanceGeneratorDelegate.java

Core symbols most depended-on inside this repo

debug
called by 117
rust/src/logger.rs
invoke
called by 89
java/src/main/java/org/astonbitecode/j4rs/api/Instance.java
create_tests_jvm
called by 82
rust/src/lib.rs
create_instance
called by 79
rust/src/api/mod.rs
to_string
called by 78
rust/src/api/mod.rs
to_rust
called by 70
rust/src/api/mod.rs
opt_to_res
called by 66
rust/src/errors.rs
collect
called by 50
rust/src/api/instance.rs

Shape

Method 428
Function 355
Class 90
Interface 15
Enum 6

Languages

Rust55%
Java45%

Modules by API surface

rust/src/cache.rs171 symbols
rust/src/api/mod.rs88 symbols
rust/src/lib.rs50 symbols
java/src/main/java/org/astonbitecode/j4rs/api/invocation/JsonInvocationImpl.java33 symbols
java/src/test/java/org/astonbitecode/j4rs/api/invocation/JsonInvocationImplTest.java30 symbols
rust/src/jni_utils.rs26 symbols
test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/MyTest.java22 symbols
rust/src/api/instance.rs22 symbols
rust/src/api/invocation_arg.rs19 symbols
rust/src/utils.rs16 symbols
java/src/main/java/org/astonbitecode/j4rs/api/instantiation/NativeInstantiationImpl.java16 symbols
rust/src/async_api/mod.rs15 symbols

For agents

$ claude mcp add j4rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact