or: fpath=(/path/to/javars/completions $fpath) in .zshrc

September 6, 2026 · View on GitHub

     ██╗ █████╗ ██╗   ██╗ █████╗ ██████╗ ███████╗
     ██║██╔══██╗██║   ██║██╔══██╗██╔══██╗██╔════╝
     ██║███████║██║   ██║███████║██████╔╝███████╗
██   ██║██╔══██║╚██╗ ██╔╝██╔══██║██╔══██╗╚════██║
╚█████╔╝██║  ██║ ╚████╔╝ ██║  ██║██║  ██║███████║
 ╚════╝ ╚═╝  ╚═╝  ╚═══╝  ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝

CI Rust license status

[JAVA, COMPILED TO BYTECODE — JIT-COMPILED, NOT WALKED — NO JVM]

"The JVM runs Java on the JVM. javars runs Java on fusevm."

Java in Rust — a Java frontend that lexes and parses Java source, lowers it to fusevm bytecode, and runs it on the shared three-tier Cranelift JIT — the same engine behind zshrs, stryke, awkrs, elisp, and ruby. No bespoke VM. No JVM. No .class files.


Table of Contents


[0x00] OVERVIEW

Every Java runtime in existence targets the JVM: javac emits .class bytecode, and a JVM (HotSpot, OpenJ9, GraalVM) interprets and JIT-compiles it. javars takes a different path — it lexes and parses Java to an AST, lowers that AST directly to fusevm bytecode, and runs it on fusevm's compiled VM with a Cranelift tracing JIT. javars carries no VM or JIT of its own; it is a pure frontend over the shared engine. Highlights:

  • Compiled, not tree-walked — arithmetic, comparisons, and control flow lower to native fusevm ops (LoadInt, Add, NumLt, JumpIfFalse, …), and every for / while / enhanced for is emitted rotated — the test as an entry guard plus a conditional backward branch — which is the shape fusevm's tracing JIT needs to close a trace, so a hot loop reaches native code. java --tiers reports whether it did.
  • fusevm-hosted, no JVM — no local vm.rs / jit.rs, no .class files, no libjvm. The same three-tier Cranelift engine that hosts zshrs, stryke, awkrs, elisp, and ruby runs Java too. jit-disk-cache persists native code across runs.
  • Java print semanticsSystem.out.print[ln] lowers to a formatting builtin so boolean prints true/false, double prints 3.0, and null prints null — matching java, not the VM's shell-flavoured default.
  • Java + overloading — a strict numeric hook supplies string concatenation ("x=" + x) for the mixed operands the VM's native arithmetic does not compute, while all-numeric arithmetic stays on the JIT fast path.
  • Real boxed wrappersInteger, Long, Short, Byte, Character, Float and Double are heap objects with the class and the JLS cache Java gives them, so Integer a = 127, b = 127; a == b is true and the same pair at 128 is false, while Integer a = 1000; Integer b = a; a == b stays true. A primitive crossing into any reference position boxes — an Object slot, a cast, a generic, an equals argument, a collection element or key — so ((Object) 1000L).getClass().getName() is java.lang.Long and a Map keyed on 1, 1.0 and 1L holds the three entries Java's holds. A String's identity is the Arc it already carries, so == on two of them is the reference comparison Java's is — "ab" == "ab" is true (one pooled literal per distinct text) while (s1 + s2) == "ab" is false, at no allocation and no indirection. Every arithmetic, rendering and hashing surface unboxes them, so the box is visible only where Java makes it visible.
  • Verified against OpenJDK — the example programs and the test corpus are diffed byte-for-byte against a reference java; the tests freeze that output so CI needs no JDK installed.

Covered today: locals, arithmetic, the C-style control statements, System.out.print[ln], user-defined static methods (recursion, parameters, value returns), String instance methods, reference arrays including multi-dimensional (new int[n], new int[m][n], {…} and {{…},{…}} literals, indexing, .length, and true pass-by-reference/aliasing on a host-owned object heap), and a class/object model (fields, constructors, instance methods, this, new, field access, single inheritance with extends, super(…) chaining and non-virtual super.member access, instanceof, virtual method dispatch, and toString() overrides). Interfaces (abstract + default methods, multiple implements, interface inheritance, polymorphic dispatch), method overloading by parameter type (most-specific resolution for methods and constructors, including Java's variable-arity phase so T... parameters take loose arguments), and type-erased generics (class Box<T>, <T> T id(T x), bounded <T extends X>, the diamond, erased library type args) all run. String.format and System.out.printf (a Formatter subset covering %d %s %S %f %e %E %g %G %b %B %h %H %x %X %o %c, all seven -/#/+/ /0/,/( flags, width, precision, and argument indexes), the mutable StringBuilder/StringBuffer (the full method surface, with capacity()'s growth modeled because it is observable), and the Arrays statics round out the stdlib essentials; the wider standard library is the next wave (see BUGS.md). Exceptions (throw/try/catch/finally, try-with-resources, and javars's own runtime faults raised as catchable throwables), static fields and static { } blocks, record types, abstract classes, and enum types — down to per-constant state (EARTH(5.97e24)) and per-constant bodies — run too, and main's args carries the real program arguments. An unsupported construct is a parse or compile error rather than a silent mis-run: there is no construct javars accepts and then runs with the wrong meaning (see BUGS.md). A program javac itself rejects is rejected here too where javars can see it — an undeclared name, and a class, field, method, constructor, enum constant, or parameter declared twice, each reported at the duplicate in javac's own wording rather than resolved silently to one of the two.


[0x01] INSTALL

git clone https://github.com/MenkeTechnologies/javars
cd javars
cargo build

# run a .java file
./target/debug/java examples/FizzBuzz.java

javars is a standalone Rust crate (an explicit empty [workspace] keeps it independent of the meta repo). fusevm is pulled from crates.io with the jit, jit-disk-cache, aot, and ffi features, and fancy-regex supplies the backtracking engine behind java.util.regex. Run the tests with cargo test (no JDK required).

Zsh tab completion

cp completions/_java /usr/local/share/zsh/site-functions/_java
# or: fpath=(/path/to/javars/completions $fpath) in .zshrc
autoload -Uz compinit && compinit

[0x02] USAGE

public class FizzBuzz {
    public static void main(String[] args) {
        for (int i = 1; i <= 15; i++) {
            if (i % 15 == 0) {
                System.out.println("FizzBuzz");
            } else if (i % 3 == 0) {
                System.out.println("Fizz");
            } else if (i % 5 == 0) {
                System.out.println("Buzz");
            } else {
                System.out.println(i);
            }
        }
    }
}
$ java FizzBuzz.java
1
2
Fizz
4
Buzz
...

[0x03] LANGUAGE FEATURES

Implemented and checked against the reference java:

  • Entry pointpublic class Name { public static void main(String[] args) { … } }. Every other member — static helpers, static fields, instance fields, constructors, instance methods — is compiled too. args is bound to the real program arguments (java Prog.java a b), and to a zero-length String[] (never null) when none are passed.
  • static fields — one cell per class, seeded with the declared type's default and then initialized by the field initializers and static { … } blocks in textual order, all before main. Reached unqualified inside the declaring class, as C.n from anywhere, and through an inheriting class; compound assignment and ++/-- write the same cell, and the field's declared type drives /-truncation and the 32-bit int wrap.
  • record typesrecord Pt(int x, int y) { … } derives the final fields, the canonical constructor, an accessor per component, toString() in Java's Pt[x=1, y=2] form, and a component-wise equals that follows JLS 8.10.3 per component kind — Double.compare/Float.compare for the floating ones (so a NaN component equals itself and 0.0 does not equal -0.0), == for the other primitives, and Objects.equals for a reference, which reaches the component class's own equals. A compact constructor validates before the fields are assigned; a member the body declares itself wins over the derived one.
  • toString() overrides, wherever a value rendersprintln(obj), "x " + obj, obj.toString(), String.valueOf(obj), Arrays.toString/deepToString, String.join, String.format("%s", obj) / "%s".formatted(obj), and every element of a List/Set/Map at any depth, whatever the receiver's static type — an Object, an erased get(), a map value. A subclass that declares none inherits its ancestor's body. The override is real code: it may print (its output comes first, before the println it was rendering for) and it may throw (the throwable propagates, and no half-built text reaches the stream). A program that declares no override compiles to byte-identical bytecode.
  • Binary type namesgetClass().getName(), the default Class@hash rendering, and a ClassCastException's head all spell Java's binary name for a nested declaration (Outer$Nested, A$B$C when doubly nested); getSimpleName() stays the simple one. A modeled JDK type reports the JDK's own spelling, private collection classes included — java.util.ArrayList, java.util.ImmutableCollections$List12 for a two-element List.of, java.util.Arrays$ArrayList, java.util.ArrayList$SubList for a view — and an array reports its descriptor ([I, [[Ljava.lang.String;, [LT$A;), which getSimpleName() decodes back to int[].
  • Abstract classesabstract class Shape { abstract double area(); … }, with super(…) chaining and concrete methods that call the abstract one (resolved to the subclass's override at runtime).
  • Localsint / long / double / boolean / String / var declarations with optional initializers; plain and compound assignment (=, +=, -=, *=, /=, %=); post-increment / post-decrement (i++, i--). A var records the type it infers, so var i = 7; i / 2 truncates and var big = 100000; big * big wraps — including the element type of a var enhanced-for over an array literal.
  • Multi-declarator declarationsint a = 1, b = 2; in statement position, in a for init clause, and as a field. Declarators are evaluated left to right, so a later one may read an earlier (int a = 1, b = a + 1;), and any of them may be left uninitialized (int a, b = 2, c;). The C-style array suffix binds to the declarator rather than the type, exactly as Java specifies, so int p[] = {1}, q; declares an int[] and an int — the suffix is accepted on locals, fields, and parameters (static int add(int xs[], int n)).
  • java.lang.Objectnew Object() is the fieldless root instance, with a distinct identity per allocation: it works as a lock, as a sentinel, and as a map key or set element. The methods every class inherits and does not override answer from Objectequals is reference identity, getClass().getName() is java.lang.Object, and toString() is the java.lang.Object@<hash> form. synchronized (m) { … } runs its body after evaluating the monitor once (javars runs one thread, so the lock itself is unobservable) and throws NullPointerException for a null monitor.
  • Expressions — integer (decimal, 0x, 0b, octal, _-separated) / floating / string / char / boolean literals, with JLS 3.3 \uXXXX escapes translated across the whole source before it is tokenized (so an escape spells an identifier or a comment as readily as a string, \\u0041 stays the two characters it is written as, and \uuuu0041 is the letter A); the binary operators + - * / %, == != < > <= >=, && || (short-circuiting), the bitwise & | ^ (Java's non-short-circuiting logical operators on booleans), and the shifts << >> >>> with Java's per-width distance masking; unary -, !, ~, and + (which changes no bits but still applies unary numeric promotion, so "" + +'A' is "65"); ++/-- in both prefix and postfix value position on a local, an array element, an instance field, or a static, evaluating the index or receiver exactly once; cast expressions with Java's saturating and two's-complement narrowing conversions (a reference cast is checked against the receiver's runtime class and throws ClassCastException); parenthesised grouping; Java's + string concatenation. A char is the 16-bit integral type it is in Java — "abc".charAt(2) + 1 is 100 and c - '0' reads a digit — and takes Java's string conversion back to a one-character String wherever one applies (println, + with a String, String.valueOf, a String-method argument). A compound assignment narrows back to its target's width, so byte b = 100; b += 100; is -56. float is Java's 32-bit type rather than an alias for double: it narrows at every operation (Java rounds once at 32 bits, so the operation runs on the host rather than being computed in 64 bits and narrowed afterwards) and prints the shortest decimal that round-trips at 32 bits, so 1.0f / 3.0f is 0.33333334. Both toStrings follow the specification rather than a plain shortest-round-trip search, which differs at the subnormal floor: the candidate set widens to two digits whenever the shortest form has one, so Double.MIN_VALUE prints 4.9E-324 and Float.MIN_VALUE prints 1.4E-45.
  • Control flowif / else if / else, while, the C-style for (init; cond; update) (with comma-separated init and update clauses), the enhanced for (T x : arr) (over an array or a collection), break, continue, and return (a bare return; ends main; return <expr>; returns a value from a method).
  • Arrow switch — as an expression (int r = switch (x) { case 1, 2 -> 10; default -> { … yield v; } };) and as a statement. Multi-label arms, enum/String/int discriminants, a throw arm, and a block arm whose yield runs any finally it leaves. The classic colon form, with its fall-through, still works unchanged.
  • Exceptionsthrow, try / catch / finally, try-with-resources, multiple catch arms matched by class, and the modeled java.lang throwable hierarchy (RuntimeException, IllegalArgumentException, NumberFormatException, …) supplied as an implicit prelude. A user class may extend any of them and reports its own runtime class wherever Java does — toString(), "" + e, println(e), and the uncaught report all render a nested class MyEx extends RuntimeException as T$MyEx: message. A throw unwinds real fusevm call frames into the caller's handler; an uncaught one reports and exits non-zero. A catch naming a throwable javars does not model is a compile error rather than an arm that can never match. A return/break/continue leaving a guarded block runs the finally on its way out, and so does an exception raised inside a catch arm.
  • enum types — constants as singletons with reference identity, name()/ordinal()/toString()/equals, values()/valueOf, unqualified switch labels, enum bodies with their own methods, and implements. A constant may carry constructor arguments (EARTH(5.97e24)) for per-constant state, or a body (PLUS { int apply(int a, int b) { … } }) — compiled to the synthetic subclass Java specifies it as, so its override (including of the enum's own abstract method) dispatches on the runtime class.
  • Catchable runtime faults — javars's own faults are Java exceptions rather than aborts: an out-of-range array index, a null receiver, Integer.parseInt on junk, integral / 0 and % 0, a negative array size, and the String index/argument faults all raise the throwable Java raises, with Java's detail message, catchable by any supertype. A null argument faults where Java faults rather than being coerced to "", and it faults with Java's class: Integer.parseInt(null) is a NumberFormatException while Double.parseDouble(null) is a NullPointerException, exactly as the JDK splits them. The String.format failures — missing argument, unknown conversion, a width or precision too large for an int, and every flag combination java.util.Formatter rejects (%,x, %#d, %+ d, %-d) — are catchable java.util.IllegalFormatExceptions too.
  • int width — Java's 32-bit int wrapping, for operations whose operand types are statically int; long stays 64-bit.
  • Division — Java's binary numeric promotion: int / int truncates toward zero (7 / 23, -7 / 2-3), and a double operand keeps the fractional result (7.0 / 23.5), decided from the operands' static types. int-width division stays on fusevm's native op pair, where computing in f64 is provably exact; a long divides in i64 through a builtin instead, so Long.MAX_VALUE / 2 is 4611686018427387903 and Long.MIN_VALUE / -1 wraps to Long.MIN_VALUE as JLS 15.17.2 specifies.
  • Static methodsstatic <ret> name(<params>) { … } compiled to fusevm's Op::Call frame ABI: parameters and locals in call-frame slots, so recursion, mutual recursion, and forward references work; void and value returns; arity checked at compile time.
  • String methods — postfix recv.method(args) dispatch on String receivers: length, isEmpty, charAt, substring, indexOf, contains, equals, equalsIgnoreCase, compareTo, compareToIgnoreCase, toUpperCase, toLowerCase, trim, startsWith, endsWith, concat, replace, repeat (chainable). compareTo is not one of them for a non-String receiver: every boxed primitive and every user Comparable declares it with a different answer (the sign, the arithmetic difference, or a user body), so it dispatches on the receiver's static type and, when that is erased, on its runtime class.
  • Regular expressionssplit (with limit), replaceAll, replaceFirst, and matches run real java.util.regex patterns on fancy-regex, whose backtracking VM is what makes Java's backreferences and lookaround expressible. src/regex.rs translates the Java source first, because the two flavours' defaults differ where it changes answers: Java's \d/\w/\s/\b are ASCII-only, its (?i) folds ASCII only, its . excludes five line terminators, and its default-mode $ matches before a final one. A construct with no faithful translation (a possessive quantifier, an atomic group, a Unicode block) raises PatternSyntaxException naming it rather than compiling into a different language.
  • Lambdas and functional interfaces() -> e, x -> e, (a, b) -> { … }, and the explicitly-typed (int a, String b) -> …. A lambda compiles to a heap closure carrying a by-value snapshot of the enclosing locals (and this), because it outlives the fusevm call frame those locals live in — which is also what gives the enhanced for Java's per-iteration capture. The target is any interface with one abstract method, Java's own rule, so a user-declared interface Calc { int of(int a); } works with no registration; Runnable, Supplier, Consumer, Function, BiFunction, Predicate, Comparator, UnaryOperator/BinaryOperator and the Int* shapes are supplied as one-method interfaces in the prelude. A functional-interface variable may hold a lambda or a class instance; the runtime-class dispatch chain routes each. return, break/continue, try/finally and throw all work inside a lambda body.
  • Method referencesString::length, Integer::parseInt, Integer::sum, Point::area, obj::method, this::method, Point::new, System.out::println. A constructor reference also names a modeled stdlib type's no-argument constructor — ArrayList::new, HashMap::new, StringBuilder::new, String::new, Object::new — which is the form a Supplier-shaped use takes.
  • java.util collectionsList/ArrayList/LinkedList, Deque/Queue/ArrayDeque, Map/HashMap/ LinkedHashMap/TreeMap, Set/HashSet/LinkedHashSet/TreeSet, the copy constructors, Arrays.asList, List.of/Set.of, and Collections.sort/reverse/max/min. A deque reads and writes at both ends (push/pop/peek, addFirst/addLast, offer*, poll*, get*/remove*/element) and distinguishes the two empty-receiver families: the get/remove/element/pop spellings throw NoSuchElementException where peek/poll answer null. removeIf and replaceAll run a predicate or operator per element and report through the receiver's shape — a List.of refuses before it looks at the argument, an Arrays.asList runs the predicate and refuses only when something must actually go. Arrays.asList is fixed-size and List.of/Set.of immutable, so a structural write to one throws UnsupportedOperationException as Java's does, and neither factory answers instanceof as the mutable kind it is not. The of factories also refuse a null — both one they are built from and one they are asked about, so List.of(1, null) and List.of(1, 2).contains(null) each throw NullPointerException rather than holding it or answering false — and an out-of-range index reports through the shape the receiver has: an ArrayList raises IndexOutOfBoundsException, an array-backed Arrays.asList or longer List.of raises the ArrayIndexOutOfBoundsException subclass, and a one- or two-element List.of raises IndexOutOfBoundsException with Java's other wording (Index: 5 Size: 2). They are heap objects like arrays, so reference semantics hold; the enhanced for iterates them; sort and forEach take a lambda. A sort is a stable merge sort driven by the comparator; naming none (Collections.sort(l), l.sort(null)) orders by the element's own compareTo, and Comparator.naturalOrder/reverseOrder/ comparing build that comparator explicitly. HashMap/HashSet iterate in Java's real bucket order(capacity - 1) & (h ^ (h >>> 16)) over a power-of-two table, reproduced exactly rather than approximated with insertion order. A membership test compares with the element's own equals(), not with identity, so list.contains(new R(1)) on a record is true, map.get finds an equal key, and a Set de-duplicates equal elements — the query is the receiver and the scan stops at the first hit, which is what Java's own does. A class that overrides equals and leaves hashCode alone is not found by a HashSet/HashMap, because it is not found by Java's either; BUGS.md has the boundary. Set.of rejects a repeated element with IllegalArgumentException rather than dropping it. A Map also answers the compound methods that are defined in terms of the primitive ones — compute, computeIfAbsent, computeIfPresent, merge, replace, putAll, and the two-parameter replaceAll — including the detail that separates them from put: a key one of the first three adds to a HashMap is linked at the head of its hash bin where put links it at the tail, so the same map filled two ways iterates in two different orders, exactly as Java's does.
  • OutputSystem.out.println(x) / System.out.print(x) with Java value formatting.
  • Inline Rust FFI — a rust { pub extern "C" fn … } block inside main compiles to a cached cdylib whose exported functions are callable by name (via fusevm::ffi); see examples/Ffi.java.
  • Comments// line, /* … */ block.

[0x04] COMMAND-LINE FLAGS

FlagEffect
FILE [args…]Run a .java file.
-version / --versionPrint the version banner and exit.
-h / --helpPrint usage and exit.
--dump-tokens FILEPrint the lexer token stream and exit.
--dump-ast FILEPrint the parsed AST and exit.
--disasm FILEPrint the lowered fusevm bytecode and exit.
--tiers FILERun it, then report which fusevm execution tier took each of its chunks.
--lspSpeak the Language Server Protocol over stdio (completion, hover, diagnostics).
--dapSpeak the Debug Adapter Protocol over stdio (breakpoints, stepping, locals).

Editor tooling

java --lsp runs a read-only language server: completion and hover from the language-reference corpus in src/reference.rs — every keyword, operator, type, library method, throwable, functional interface, synthesized class member, String.format conversion, and runtime builtin the build implements, each with its signature, a description, and an example — and diagnostics from the runtime's own parser, where a syntax error maps to a diagnostic on the reported line. The same table generates docs/reference.html, so the editor and the published reference cannot drift apart.

java --dap runs a Debug Adapter over stdio: line breakpoints, single-stepping (next / stepIn / stepOut advance to the next statement in the single main frame), a stackTrace of the current frame, and variables inspection of main's locals. The program is compiled with per-statement line markers only in this mode; System.out output is captured and forwarded as output events so it never corrupts the protocol channel.

java --version reports the targeted language level (java 21) followed by the real engine (javars <crate-version>) and the host triple, so nothing is misrepresented as the JDK.

Inline Rust FFI

A rust { … } block inside main embeds native Rust in a Java program:

public class Ffi {
    public static void main(String[] args) {
        rust { pub extern "C" fn j_triple(x: i64) -> i64 { x * 3 } }
        System.out.println(j_triple(14));   // => 42
    }
}

Before lexing, the block is rewritten to a __rust_compile("<base64>", line) call; the runtime hands the base64-encoded body to fusevm::ffi, which compiles it to a cdylib (cached by content hash) and registers its pub extern "C" exports. A bareword call whose name is not a local resolves to an FFI export by name — but only when the program contains a rust { … } block; without one, an unknown call stays an ordinary unresolved reference compile error.


[0x05] ARCHITECTURE

javars contains no virtual machine or JIT of its own. The execution path mirrors how zshrs hosts zsh and ruby hosts Ruby:

Java source → lexer → parser (AST) → lower to fusevm bytecode → fusevm VM + Cranelift JIT

                              strict numeric hook (Java `+` concat)
                              print builtins (Java value formatting)
PieceHow
fusevm-hostedNo local vm.rs / jit.rs, no JVM. Java lowers to fusevm bytecode and runs on the shared three-tier Cranelift JIT; jit-disk-cache persists native code across runs.
Native arithmeticOperators lower to native fusevm ops; the JIT traces hot integer loops. A strict numeric hook supplies Java's + string concatenation only for non-numeric operands.
Java print semanticsSystem.out.print[ln] lowers to a registered builtin that formats values Java-style (true/false, 3.0, null), rather than the VM's shell-flavoured PrintLn.

[0x06] STATUS & ROADMAP

This release: main, locals (including multi-declarator statements and the C-style array suffix, int a = 1, b[] = {2};), arithmetic / comparison / logic, Java integer-vs-float division, the ternary ?: operator, if / while / do-while / for / switch (with fall-through, on int and String) / break / continue (including labeled break outer; / continue outer;) / return, the bitwise and shift operators (& | ^ ~ << >> >>> and their compound forms), cast expressions, ++/-- in value position, System.out/System.err print[ln], string concatenation, user-defined static methods (recursion, parameters, value returns over fusevm's Op::Call frame ABI), String instance methods, a first slice of the standard library (Math.*, the Integer/Long/Double parsing, radix, bit-twiddling (bitCount, reverse, reverseBytes, highestOneBit, lowestOneBit, numberOfLeading/TrailingZeros, rotateLeft/rotateRight, each answering at its declared width) and constant statics, Boolean.parseBoolean, the Character predicates, the java.util.Objects statics (hashCode/hash/toString/isNull/nonNull/equals/ requireNonNull/requireNonNullElse, each answering for a null where the instance method throws), String.valueOf/join/format, String.chars/ codePoints/lines, System.out.printf, and the Arrays statics including sort/fill/copyOf/deepToString), reference arrays including multi-dimensional (default-valued new T[n] / new T[m][n], {…} and nested {{…},{…}} literals, get/set indexing, .length at each level, and reference/aliasing semantics on a host-owned object heap keyed by Value::Obj), a class/object model (instance fields with initializers, constructors, instance methods dispatched over the frame ABI, this, new, field access obj.f, single inheritance with extends and super(…), the non-virtual super.method(args)/super.field access an override uses to reach the version it overrides, instanceof over every shape the value model names (user classes, boxed primitives, collections, arrays, enum as Enum and record as Record) with the checked reference cast reading that same answer, runtime-class virtual dispatch for overrides, and toString() overrides honoured wherever a value renders), interfaces (abstract + default methods, multiple implements, interface extends, polymorphic dispatch through an interface type), method overloading by parameter type (most-specific resolution for methods and constructors, plus Java's third — variable-arity — phase, so a T... parameter is callable with loose arguments at every call site), type-erased generics (class Box<T>, <T> T id(T x), bounded <T extends X>, the diamond), static fields with static { } initializer blocks, instance initialization in JLS 12.5 order (superclass constructor, then this class's field initializers and bare { } blocks in textual order, then the constructor body — so a virtual call made from a superclass constructor sees the subclass's fields at their defaults, as Java specifies; plus the implicit super() every constructor without an explicit chain runs, and this(…) delegation, which runs the initializers exactly once), record types with their derived accessors / toString / equals, abstract classes, enum constants carrying state or bodies, and lambdas + method references (heap closures capturing by value, dispatched through any single-abstract-method interface), and the java.util collections (List/Map/Set with Java's real HashMap bucket iteration order, and List.subList as a genuine aliasing view — writes cross in both directions and a backing list modified behind it raises ConcurrentModificationException), arrow switch expressions with yield, and java.lang.Object (new Object() as a lock or sentinel, plus the equals/hashCode/toString/ getClass every class inherits from it) — all verified byte-for-byte against OpenJDK.

The object heap lives host-side in src/host.rs: Value::Obj(u32) is an opaque handle into a frontend-owned slab of arrays and instances (the same pattern the ruby/node/php frontends use), so identity and aliasing are real rather than value-copied.

Next waves, in priority order:

  1. Streams.stream().map(…).filter(…), IntStream.range, the collect/reduce/findFirst terminals. The lambdas they take already work, as do the functional interfaces' default composition methods and statics (Function.andThen, Predicate.negate, Comparator.reversed/ naturalOrder/comparing). What is missing is the surface itself — the sources, the intermediate operations with Java's laziness, the terminals, and Optional. A host builtin holds &mut VM and can re-enter it (that is how forEach, sort with a comparator, and a user toString() already run user code), so a stream can be a host object driving each stage's closure per element; compile-time pipeline fusion is one way to build it, not a prerequisite. See BUGS.md for which callback shapes can re-enter and which cannot.
  2. Map.entrySet and the remaining collection views (List.listIterator), plus wider stdlib coverage (a record's derived hashCode, Collectors.toCollection). The pattern forms this line used to list — case Integer i ->, case null, when guards — and Iterator all run; measured against the reference on openjdk 21.0.12.1.
  3. Lazy class initialization — javars runs every class's static initializers before main; Java runs each class's on first use.

See BUGS.md for the honest known-gaps list.


[0xFF] LICENSE

MIT — free and open source. See LICENSE.