Use Java 13 syntax in GraalVM with Jabel

GraalVM’s native-image is great, but (at least at the time this post was published) it only supports Java 8.

Since Java 8, we got new language features like var, switch expressions, Milling of Project Coin and others.

But, if we want to use these features, Java compiler leaves no option other than producing Java 12+ bytecode, even tho they don’t need it and can be compiled to Java 8.

If only there was a tool that will unlock these new syntax while targeting Java 8…

Meet Jabel

A few days ago I opensourced Jabel - a hack that instruments the java compiler and makes it treat some new Java 9+ language features as they were supported in Java 8.

Consider the following Java 13 code:

package com.example;

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<Integer, String> callable = (var length) -> switch(args.length) {
            case 1 -> "one";
            case 2 -> "two";
            default -> """
                What is "length" anyways?
                Should we even bother trying to calculate it?
                Whatever.. the number was:
                """ + args.length;
        };

        System.out.println(callable.apply(args.length));
    }
}

We use three language features that are not available in Java 8 (can you name all 3?).

Now, let’s add Jabel to our Maven file:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <!-- Make sure we're not using Java 9+ APIs -->
                <release>8</release>
                <!-- Jabel must be added to the annotation processor paths -->
                <annotationProcessorPaths>
                    <annotationProcessorPath>
                        <groupId>com.github.bsideup.jabel</groupId>
                        <artifactId>jabel-javac-plugin</artifactId>
                        <version>0.2.0</version>
                    </annotationProcessorPath>
                </annotationProcessorPaths>
                <annotationProcessors>
                    <annotationProcessor>
                    com.github.bsideup.jabel.JabelJavacProcessor
                    </annotationProcessor>
                </annotationProcessors>
            </configuration>
        </plugin>
    </plugins>
</build>

<repositories>
    <!-- Don't forget Jitpack! -->
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

From now on, if we compile that class, it should be a valid Java 8 classfile:

$ mvn -q package
Jabel: initialized. Enabled features:
	- TEXT_BLOCKS
	- SWITCH_MULTIPLE_CASE_LABELS
	- VAR_SYNTAX_IMPLICIT_LAMBDAS
	- PRIVATE_SAFE_VARARGS
	- LOCAL_VARIABLE_TYPE_INFERENCE
	- EFFECTIVELY_FINAL_VARIABLES_IN_TRY_WITH_RESOURCES
	- DIAMOND_WITH_ANONYMOUS_CLASS_CREATION
	- SWITCH_EXPRESSION
	- SWITCH_RULE
$ $JAVA8_HOME/bin/java -cp target/classes com.example.Main foo
one

Trying it with GraalVM’s native-image

Native Image tool says that it can AOT compile most of Java 8 code into a native executable, let’s check:

[/tmp/graalvm-jabel-example]$ $GRAALVM_HOME/bin/native-image -cp target/classes/ com.example.Main
Build on Server(pid: 98111, port: 49591)
[com.example.main:98111]    classlist:     130.87 ms
[com.example.main:98111]        (cap):   1,070.62 ms
[com.example.main:98111]        setup:   1,461.72 ms
[com.example.main:98111]   (typeflow):   1,438.00 ms
[com.example.main:98111]    (objects):   1,585.12 ms
[com.example.main:98111]   (features):     158.52 ms
[com.example.main:98111]     analysis:   3,225.99 ms
[com.example.main:98111]     (clinit):      53.44 ms
[com.example.main:98111]     universe:     138.85 ms
[com.example.main:98111]      (parse):     110.76 ms
[com.example.main:98111]     (inline):     531.70 ms
[com.example.main:98111]    (compile):   1,274.18 ms
[com.example.main:98111]      compile:   2,060.94 ms
[com.example.main:98111]        image:     166.56 ms
[com.example.main:98111]        write:     135.31 ms
[com.example.main:98111]      [total]:   7,380.42 ms

[/tmp/graalvm-jabel-example]$ ./com.example.main foo
one

[/tmp/graalvm-jabel-example]$ ./com.example.main foo bar
two

[/tmp/graalvm-jabel-example]$ ./com.example.main foo bar baz
What is "length" anyways?
Should we even bother trying to calculate it?
Whatever.. the number was:
3

It works 🎉

Conclusion

I would like to give thanks to Dave Rusek who pointed me to the GraalVM use case of Jabel!

As you can see, with a minimal effort you can start using Java 12 or even 13 syntax in your GraalVM native apps.

comments powered by Disqus