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, structs, traits, and function pointers lower to ordinary JVM classes and interfaces rather than opaque native handles (see Interop Model). This enables unusually direct interop:
&dyn Trait (test and demo).Fn closures
(test and demo).async functions from Kotlin suspend code while retaining
Kotlin's coroutine dispatcher
(test and demo).For example, one Rust API can expose an enum and accept both a JVM implementation of a Rust trait and a standard JVM lambda. Its result can then cross the Rust/Kotlin async bridge. The complete example is kept executable in the Kotlin interop test suite:
Rust
pub trait BatchObserver {
fn accept(&mut self, processed: u32) -> bool;
}
pub enum PipelineResult {
Success { count: u32, elapsed_ms: u64 },
Rejected(i32),
}
pub fn process_batch(
batch_size: u32,
transform: &dyn Fn(u32) -> u32,
observer: &mut dyn BatchObserver,
) -> PipelineResult {
let processed = transform(batch_size);
if observer.accept(processed) {
PipelineResult::Success {
count: processed,
elapsed_ms: u64::from(batch_size),
}
} else {
PipelineResult::Rejected(-1)
}
}
pub async fn confirm_batch(result: PipelineResult) -> PipelineResult {
result
}
Kotlin
import my_crate.BatchObserver
import my_crate.PipelineResult
import org.rustlang.runtime.await
class LimitObserver(private val limit: Int) : BatchObserver {
override fun accept(processed: Int): Boolean = processed <= limit
}
suspend fun main() {
val prepared = my_crate.my_crate.process_batch(
40,
{ value -> value + 2 },
LimitObserver(100),
)
val outcome = my_crate.my_crate.confirm_batch(prepared)
.await<PipelineResult>()
when (outcome) {
is PipelineResult.Success -> {
val (count, elapsedMs) = outcome
println("completed: $count in ${elapsedMs}ms")
}
is PipelineResult.Rejected -> {
val (code) = outcome
println("rejected: $code")
}
else -> error("unknown PipelineResult implementation")
}
}
Single tuple payloads use value, multi-field tuples use _0, _1, and so on,
and struct-like variants retain their field names. Kotlin can destructure any payload variant.
Expanded Java/Rust example covering methods, callbacks, enum subtypes, constructors, and fields
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).value;
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
Add jvm = { package = "rcj", git = "https://github.com/IntegralPilot/rustc_codegen_jvm" }
to [dependencies].
#![feature(extern_types, register_tool)]
#![register_tool(jvm_codegen)]
#[jvm::class("java.time.LocalDate", rename_all = "camelCase")]
impl JavaLocalDate {
#[jvm::static_method]
pub fn of(year: i32, month: i32, day: i32) -> *mut Self {}
#[jvm::method]
pub fn get_year(&self) -> i32 {}
}
#[jvm::class("Main.JavaCounter", rename_all = "camelCase")]
impl JavaCounter {
#[jvm::constructor]
pub fn new(value: i32) -> *mut Self {}
#[jvm::field]
pub fn value(&self) -> i32 {}
#[jvm::field]
pub fn set_value(&mut self, value: i32) {}
#[jvm::static_field(class = "Main")]
pub fn shared_count() -> i32 {}
#[jvm::static_field(class = "Main")]
pub fn set_shared_count(value: i32) {}
}
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_codegen::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 {
JavaLocalDate::of(year, month, day)
}
pub fn java_date_year(date: &JavaLocalDate) -> i32 {
date.get_year()
}
pub fn update_java_counter() -> i32 {
unsafe {
let counter = JavaCounter::new(5);
(&mut *counter).set_value((&*counter).value() + 1);
JavaCounter::set_shared_count(JavaCounter::shared_count() + 1);
(&*counter).value() + JavaCounter::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) introduce
browse all types & interfaces →
$ claude mcp add rustc_codegen_jvm \
-- python -m otcore.mcp_server <graph>