Browse by type
A custom Rust compiler backend that compiles Rust directly to Java Virtual Machine (JVM) bytecode, enabling you to compile crates into a runnable .jar compatible with Java 8+.

This backend transparently compiles Rust constructs to Java classes and interfaces, enabling rich interop between JVM and Rust code at a level mostly unreachable by traditional FFI solutions.
It also enables modern Rust code to run on older platforms outside the reach of current native targets, and has integrated upstream changes into OpenJDK's C2 JIT compiler which can make the JVM faster for everyone, including ~1.85x faster 128-bit multiplication on x86.
By leveraging a "virtual MMU" translation layer, it supports raw pointers with complex pointer arithmetic, transmute, and unions. It also supports key parts of the Rust standard library, including networking, async/await, threading, unwinding, allocation, as well as file system operations, STDIO, and more.
Every selected official Rust coretests and alloctests test passes in CI in both debug and release mode. CI currently verifies 2,812 of 2,817 coretests and 1,474 of 1,477 alloctests, which is roughly 99.8% of the upstream test suites.
[!NOTE] This project is in an active mid-stage of development. While it supports the vast majority of the Rust language, edge-case bugs are continually being ironed out. The ultimate goal is potential upstreaming into main
rustc.
Stars, contributions, and feedback are highly welcome and appreciated!
cargo-jvmIf on Windows, please use PowerShell so $PWD will work.
git clone https://github.com/IntegralPilot/rustc_codegen_jvm
cd rustc_codegen_jvm
cargo install --path cargo-jvm
cargo jvm setup "$PWD"
cargo-jvm installs and selects the backend's pinned Rust nightly automatically;
your default toolchain can remain stable.
cargo new hello_world --bin
cd hello_world
cargo jvm run
You should see "Hello, world!" printed to the console.
Then, head down to Usage to learn how to integrate it into your project.
Rust enums, generics, function pointers, unions, and traits map directly onto JVM classes and interfaces (see Interop Model). Because of this, rustc_codegen_jvm achieves a level of ergonomic interop with Java that native FFI solutions cannot easily match. For example, you can implement a Rust trait directly on a Java class and pass it as &dyn Trait to Rust (test and demo), or pass a standard Java lambda directly to a Rust function expecting a Fn closure (test and demo).
Java
import org.rustlang.runtime.Utf8View;
import my_crate.NamedCounter;
import my_crate.Accumulator;
import my_crate.Calculation;
import my_crate.NetworkEvent;
import my_crate.AppEvent;
import java.time.LocalDate;
import static my_crate.my_crate.*;
public class Main {
// Implement a Rust trait directly on any Java class
private static class JavaAccumulator implements Accumulator {
private int sum = 0;
@Override
public int add(int amount) {
this.sum += amount;
return this.sum;
}
}
// Ordinary Java fields and constructors can be imported by Rust.
public static int sharedCount = 10;
public static final class JavaCounter {
public int value;
public JavaCounter(int value) {
this.value = value;
}
}
public static void main(String[] args) {
// 1. Interact with Rust types and methods
NamedCounter counter = NamedCounter.new(Utf8View.fromJavaString("JVM-Counter"));
counter.increment();
System.out.println("Counter: " + counter.count);
// 2. Construct, inspect, compare, and call methods on Rust enums
Calculation calculation = new Calculation.Success(42);
Calculation sameCalculation = new Calculation.Success(42);
int payload = ((Calculation.Success) calculation).field0;
System.out.println("Enum payload: " + payload);
System.out.println("Enum method: " + calculation.value_or(-1));
System.out.println("Enum equality: " + Calculation.eq(calculation, sameCalculation));
// A transparent enum subtype needs no AppEvent.Network wrapper.
NetworkEvent network = new NetworkEvent.Connected(8080);
AppEvent event = network;
System.out.println("Outer variant: " + AppEvent.variantIndex(event));
System.out.println("Trait method: " + event.code());
System.out.println("Rust match: " + inspect_event(event));
// 3. Pass a standard Java lambda directly to a Rust Fn closure
int result = apply_twice(val -> val * 3, 2);
System.out.println("Lambda output: " + result);
// 4. Pass a Java trait implementation to Rust dynamic dispatch
JavaAccumulator acc = new JavaAccumulator();
int finalSum = run_accumulation(acc);
System.out.println("Accumulator sum: " + finalSum);
// 5. Construct and call a standard Java API object in Rust
LocalDate leapDay = make_java_date(2024, 2, 29);
System.out.println("Leap day: " + leapDay);
System.out.println("Leap year: " + java_date_year(leapDay));
// 6. Construct a Java object and access its fields from Rust
System.out.println("Java field result: " + update_java_counter());
}
}
Rust
#![feature(extern_types, register_tool)]
#![register_tool(jvm)]
unsafe extern "C" {
#[link_name = "java/time/LocalDate"]
pub type JavaLocalDate;
#[link_name = "jvm:static:java/time/LocalDate:of"]
fn java_local_date_of(year: i32, month: i32, day: i32) -> *const JavaLocalDate;
#[link_name = "jvm:virtual:getYear"]
fn java_local_date_get_year(date: &JavaLocalDate) -> i32;
#[link_name = "Main$JavaCounter"]
pub type JavaCounter;
#[link_name = "jvm:new:Main$JavaCounter"]
fn java_counter_new(value: i32) -> *mut JavaCounter;
// A return value makes this an instance-field getter.
#[link_name = "jvm:field:value"]
fn java_counter_value(counter: &JavaCounter) -> i32;
// A value parameter and () return make this an instance-field setter.
#[link_name = "jvm:field:value"]
fn java_counter_set_value(counter: &mut JavaCounter, value: i32);
#[link_name = "jvm:static-field:Main:sharedCount"]
fn shared_count() -> i32;
#[link_name = "jvm:static-field:Main:sharedCount"]
fn set_shared_count(value: i32);
}
impl JavaLocalDate {
pub fn year(&self) -> i32 {
unsafe { java_local_date_get_year(self) }
}
}
pub struct NamedCounter {
pub name: &'static str,
pub count: u32,
}
impl NamedCounter {
pub fn new(name: &'static str) -> Self {
NamedCounter { name, count: 0 }
}
pub fn increment(&mut self) {
self.count += 1;
}
}
pub enum Calculation {
Success(i32),
Failure(i32),
}
impl Calculation {
pub fn value_or(&self, fallback: i32) -> i32 {
match self {
Calculation::Success(value) => *value,
Calculation::Failure(_) => fallback,
}
}
}
pub enum NetworkEvent {
Connected(i32),
Disconnected,
}
pub enum AppEvent {
// NetworkEvent extends AppEvent on the JVM; AppEvent$Network is omitted.
#[jvm::subtype]
Network(NetworkEvent),
Calculation(Calculation),
}
pub trait EventCode {
fn code(&self) -> i32;
}
impl EventCode for AppEvent {
fn code(&self) -> i32 {
match self {
AppEvent::Network(NetworkEvent::Connected(port)) => *port,
AppEvent::Network(NetworkEvent::Disconnected) => -1,
AppEvent::Calculation(value) => value.value_or(-1),
}
}
}
pub fn inspect_event(event: AppEvent) -> i32 {
event.code()
}
pub fn apply_twice(callback: &dyn Fn(i32) -> i32, value: i32) -> i32 {
callback(callback(value))
}
pub trait Accumulator {
fn add(&mut self, value: i32) -> i32;
}
pub fn run_accumulation(acc: &mut dyn Accumulator) -> i32 {
acc.add(10) + acc.add(5)
}
pub fn make_java_date(year: i32, month: i32, day: i32) -> *const JavaLocalDate {
unsafe { java_local_date_of(year, month, day) }
}
pub fn java_date_year(date: &JavaLocalDate) -> i32 {
date.year()
}
pub fn update_java_counter() -> i32 {
unsafe {
let counter = java_counter_new(5);
java_counter_set_value(&mut *counter, java_counter_value(&*counter) + 1);
set_shared_count(shared_count() + 1);
java_counter_value(&*counter) + shared_count()
}
}
Because the compiler targets standard JVM bytecode rather than native machine code, compiled output can run on platforms far outside the reach of modern native Rust targets. It supports any environment with JVM 8+ compatibility.
| Operating System | Native Rust Minimum | JVM 8 (rustc_codegen_jvm) |
|---|---|---|
| Windows | Windows 10 | Windows Vista SP2 / 7 SP1 |
| macOS | 10.12 Sierra | 10.8.3 Mountain Lion |
| Linux | Kernel 3.2, glibc 2.17 | Kernel 2.6.28, glibc 2.9+ |
| Solaris | Solaris 11.4 | Solaris 10 |
Compiling directly to JVM bytecode also avoids the deployment friction of native shared libraries in restricted environments. This makes compiled JARs highly portable across sandboxed environments (such as Minecraft mod loaders) and Android platforms (via DEX conversion).
Developing this backend helps inspire me to find opportunities to optimise OpenJDK's upstream HotSpot C2 compiler. Contributions benefit the entire JVM ecosystem (including Java and Kotlin).
One merged optimisation (OpenJDK PR #30174) sped up 128-bit multiplication by ~1.85x on x86 targets. Another contribution under review (OpenJDK PR #30485) introduces internal range-check elimination in loops for common compiled patterns.
Transitioning a large production JVM codebase to native Rust is rarely feasible in a single step. rustc_codegen_jvm enables an incremental migration path where new or refactored components are written in Rust while remaining fully compatible with the existing JVM application. Once a rewrite is complete, the Rust code can either be target-switched to native or kept on the JVM target for fast iteration and cross-platform consistency.
Once shared standard-library artifacts are cached, incremental compilation for crates is fast. Leveraging the JVM's mature debugging, hot-reloading, and tracing ecosystem (such as JFR and IDE debuggers) opens up rapid iteration workflows that are traditionally difficult with native Rust targets.
Additionally, the virtual MMU layer can catch raw pointer Undefined Behavior (UB) early, throwing structured Java exceptions with accurate stack traces and LineNumberTable information.
The following example programs live in tests/, are compiled with the standard library to JVM bytecode, and are verified in CI on every commit:
| Example | Demonstrates |
|---|---|
| Alloc | Complex allocations: binary trees, heaps, linked lists, vectors, strings, Arc/atomics, and drop/cleanup semantics. |
| Threads | Multi-threading, scoped threads, mutexes (with poisoning), RWLocks, barriers, condition variables, and TLS. |
| Panic | Unwinding, catching static/dynamic panic payloads, resuming unwinds, and custom panic hooks. |
| Async / Await | Multi-poll futures, nested and recursive async work, async closures and trait methods, dyn Future, cross-thread execution, cancellation, and unwinding across suspension points. |
| STD | File system operations, command-line arguments, environment variables, standard I/O, and runtime context. |
| **[ |
browse all types & interfaces →
$ claude mcp add rustc_codegen_jvm \
-- python -m otcore.mcp_server <graph>