Elvelt

Frontends

Java Frontend

Java Frontend

The Java frontend is a BraceFrontendBase subclass delivered as a single 1,047-line header. Like PHP and Rust, it implements modern Java features through a pipeline of seven static source-rewrite transforms that run before the brace parser sees the source.

File Lines Purpose
frontends/java/JavaFrontend.h 1,047 Frontend class with all method bodies inline
frontends/java/JavaStdLib.h 78 findStdLibMapping for System.out.*, Math.*, Integer.parseInt, etc.

Language registration

// JavaFrontend.h:9-10
std::string languageName() const override { return "java"; }
std::vector<std::string> fileExtensions() const override { return {".java"}; }

Only .java is recognised.


Preprocessing pipeline

JavaFrontend::parse (line 12-23) applies the rewrites in this order:

Order Transform Lines Purpose
1 rewriteJavaTextBlocks 736-779 Lower """...""" text blocks to string literals
2 stripJavaMethodModifiers 392-423 Strip public, private, protected, static, final, abstract, synchronized from method headers
3 stripJavaInterfaceMethodDeclarations 529-597 Interface method bodies become stubs
4 rewriteJavaRecordsToClasses 599-735 record Point(int x, int y)class Point { int x; int y; ... }
5 stripJavaVarFromLambdaParams 204-257 (var x, var y)(x, y) in lambda params
6 rewriteJavaArrowToJsArrow 904-941 ->=> so the brace parser sees lambda arrow
7 rewriteJavaVarDecls 951-1043 Type x = expr;var x = expr;

// JavaFrontend.h:26-31
return {
    {"System.out.println", true, true},
    {"System.out.print", true, false}
};

System.err.println and System.err.print are mapped via the JavaStdLib table.


Features (file:line)

Text blocks (Java 15+)

String html = """
        <html>
            <body>
                <p>Hello, %s!</p>
            </body>
        </html>
        """;

rewriteJavaTextBlocks (line 736) implements a subset of JLS §13.5.6:

  1. Strip common leading whitespace (relative to the closing """).
  2. Strip blank trailing lines.
  3. Replace embedded \s (where supported) — not yet implemented.
  4. Rebuild the content as a single string literal (line 825).

Lowered to:

String html = "<html>\n    <body>\n        <p>Hello, %s!</p>\n    </body>\n</html>";

var keyword (Java 10+)

tryParseVarDecl (line 37) handles both var x = expr; and the type-prefixed form via rewriteJavaVarDecls (line 951):

var list = new ArrayList<String>();
String name = "Alice";
Child c = new Child();

is rewritten to:

var list = new ArrayList<String>();
var name = "Alice";
var c = new Child();

so the brace parser's splitEmbeddedStatementStart doesn't split the declaration into a stray type expression.

Records (Java 16+)

rewriteJavaRecordsToClasses (line 599) rewrites:

public record Point(int x, int y) {
    public double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

into:

public class Point {
    int x;
    int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

Sealed classes (Java 17+)

sealed class Shape permits Circle, Square { … } is parsed as a regular class. The sealed, permits, final, non-sealed keywords on class members are stored in metadata but not enforced.

Pattern matching for instanceof (Java 16+)

if (obj instanceof String s) {
    System.out.println(s.length());
}

stripJavaMethodModifiers and the brace parser handle this — s is emitted as a VariableDeclNode whose value is the cast expression.

Pattern matching for switch (Java 21+)

String formatted = switch (obj) {
    case Integer i -> "Integer: %d".formatted(i);
    case String s  -> "String: %s".formatted(s);
    case null      -> "null";
    default        -> obj.toString();
};

Switch expressions with patterns are recognised as ordinary switch statements. The pattern case Integer i becomes a case with a VariableDeclNode binding. Type patterns (case Type var name) work.

-> lambda-form switch expressions are converted to => via rewriteJavaArrowToJsArrow.

Virtual threads (Java 21+)

Thread.startVirtualThread(() -> {
    doWork();
});

Recognised as a normal method call; no special lowering for the virtual thread semantics (see limitations.md).

Lambda expressions (Java 8+)

list.forEach(s -> System.out.println(s));
list.removeIf((String s) -> s.isEmpty());
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

Java's -> is rewritten to => by rewriteJavaArrowToJsArrow (line 904). The lambda parser produces a LambdaNode with expressionBody for arrow-with-expression and body for arrow-with-block.

(var x, var y) -> … (Java 11+)

stripJavaVarFromLambdaParams (line 204) finds every ( var -> region and strips the var keyword so the brace parser sees plain identifier parameters.

var for lambda parameters is the only Java-11+ syntactic form; type

annotations inside lambda parameters ((String s) -> …) are accepted.


Top-level statement kinds

tryParseLanguageSpecificStatement (line 94-191) handles:

Statement File:line IR
package com.foo; 104-110 BlockNode(metadata["java_package"]="com.foo")
import com.foo.Bar; 113-119 ImportNode(module="com.foo.Bar")
interface Name { … } 122-131 InterfaceDeclNode(name="Name")
enum Name { A, B, C } 134-155 EnumDeclNode(name="Name")
(args) -> body / arg -> body 158-188 LambdaNode

Method declarations

tryParseFunctionDeclHeader (line 48) strips public, private, protected, static, final, abstract, synchronized from the header. The brace parser then handles the resulting line.

The body of the method is parsed recursively by BraceFrontendBase.


Standard library mapping (JavaStdLib.h)

The complete table at frontends/java/JavaStdLib.h:40-67:

Java MPL module.function
System.out.println(...) Console.WriteLine(...)
System.out.print(...) Console.Write(...)
System.err.println(...) Console.ErrorWriteLine(...)
System.err.print(...) Console.ErrorWrite(...)
Math.abs(x) Math.Abs(x)
Math.floor(x) Math.Floor(x)
Math.ceil(x) Math.Ceil(x)
Math.round(x) Math.Round(x)
Math.sqrt(x) Math.Sqrt(x)
Math.pow(x, y) Math.Pow(x, y)
Math.max(a, b) Math.Max(a, b)
Math.min(a, b) Math.Min(a, b)
Math.log(x) Math.Log(x)
Math.sin(x) Math.Sin(x)
Math.cos(x) Math.Cos(x)
Math.tan(x) Math.Tan(x)
Math.random() Math.Random()
System.exit(code) Process.Exit(code)
System.getenv(name) Environment.GetVariable(name)
System.getProperty(name) Environment.GetVariable(name)
Integer.parseInt(s) Convert.ToInt(s)
Double.parseDouble(s) Convert.ToFloat(s)
String.valueOf(x) Convert.ToString(x)

IR examples

Example 1 — record

public record Point(int x, int y) {
    public double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

After rewriteJavaRecordsToClasses:

public class Point {
    int x;
    int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

After rewriteJavaVarDecls (no Type x = expr; to rewrite here).

After stripJavaMethodModifiers (no modifiers to strip).

Lowers to:

ClassDeclNode(name="Point",
  metadata=["java_record"="true",
            "java_record_components"="int,x;int,y"])
  members:
    VariableDeclNode(name="x", class_field="true")
    VariableDeclNode(name="y", class_field="true")
  constructor:
    FunctionDeclNode(name="Point", isConstructor=true,
      parameters=[("auto","x"),("auto","y")])
  methods:
    FunctionDeclNode(name="distance",
      parameters=[],
      body=StandardLibraryCallNode("Math","Sqrt",
        BinaryOpNode("+",
          BinaryOpNode("*", IdentifierNode("x"), IdentifierNode("x")),
          BinaryOpNode("*", IdentifierNode("y"), IdentifierNode("y")))))

Example 2 — text block

String html = """
        <html>
            <body>Hello</body>
        </html>
        """;

After rewriteJavaTextBlocks:

String html = "<html>\n    <body>Hello</body>\n</html>";

After rewriteJavaVarDecls:

var html = "<html>\n    <body>Hello</body>\n</html>";

Example 3 — pattern matching for switch

static String describe(Object obj) {
    return switch (obj) {
        case Integer i -> "Integer %d".formatted(i);
        case String s  -> "String %s".formatted(s);
        case null      -> "null";
        default        -> "unknown";
    };
}

-> becomes => (line 904). Switch becomes SwitchNode(expr=obj). Pattern cases with bindings become SwitchCase with a binding extractor in the body.


What is not supported (Java-specific gaps)

  • Real sealed class enforcement: permits is parsed but not used to validate that the listed types are the only subclasses.
  • Virtual threads: Thread.startVirtualThread(...) is an ordinary call — no coroutine semantics.
  • Scoped values (Java 21+ preview): not supported.
  • Structured concurrency (Java 21+ incubator): not supported.
  • String templates (Java 21+ preview, JEP 459): STR."Hello \{name}" is parsed as an ordinary string concatenation.
  • Implicitly declared classes and instance main methods (Java 21+ preview): void main() is parsed as a regular method.
  • Foreign Function & Memory API: java.lang.foreign references pass through; no FFM bindings are emitted.
  • record canonical constructor overrides: the canonical ctor is generated automatically; explicit compact constructors (public Point { … }) are not specially recognised.