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.