Native javac built with GraalVM

In my previous article, I showed you how you can make Apache Maven builds faster by tuning the JVM using MAVEN_OPTS . In this article, we’re looking at if we can make builds faster by compiling javac into a native executable using GraalVM native-image.

The chart above is from invoking javac on the main sources of Apache Commons Collections. The native javac executable reduced compile times by between 37% to 87% relative to the non-native versions. As you can see tuning the JVM makes a big difference, but the native javac executable is still much faster.

Compiling native javac

You can’t compile javac directly with native-image as javac needs the system properties that point to where it can find the JAR files that make up the JDK. The following class adds the missing properties:

Note: for readability, this is actually a simplified version of the class used in the benchmark.

To build the native executable you need GraalVM (the easiest way to install GraalVM is with SDKMAN!).

The following script will build the native executable:

Note: to squeeze a bit more performance, and minimize binary size, I added two additional command line flags when building javac-native for the benchmark. -H:-MultiThreaded this removes unused support for multi-threading. -H:+NativeArchitecture this compiles using the features of the local CPU.

Be aware this native javac doesn’t support annotation processors such as Project Lombok. If required, you could probably include the annotation processors when building the native executable.

Using native javac with Maven

You need javac-native to be under a directory in the PATH and to have set the GRAAL_HOME environment variable to be the root of the GraalVM SDK.

You need to configure the maven-compiler-plugin to fork and execute javac-native, the easiest way to do this is in your Maven ~/.m2/settings.xml:

Maven build times

Since few people use javac directly let’s see what effect this has on Maven build times:

The native javac executable reduced build time by between 5% to 16%.

Given the unit tests consume most of the build time lets see what the results look like when we skip the test execution:

Without the tests, the native javac executable reduced build times by between 12% to 39%.

As you can see the native javac executable significantly improves build times across the board.

Tuned MAVEN_OPTS

Here are the MAVEN_OPTSfor the tuned builds.

Test methodology

You can find the scripts I used for the benchmarks and other details in this GitHub project.

Conclusions

The adventurous of you are probably wondering if you can use this to make your existing local or CI builds faster. I think executables built with GraalVM native-image have the potential to change the way we build Java applications in a much more fundamental way.

Java build tools (e.g. Maven and Gradle) are complicated and opinionated beasts. Most build tools for other languages are relatively simple pipelines for invoking a series of native executables. Java build tools are designed to avoid forking new JVMs as much as possible (due to the high performance and RAM cost). This means each Java build tool has to deal with complicated class loading issues and requires a plugin API. It also means most tools invoked during a Java build end up having to write a command line API, a Maven plugin and a Gradle plugin.

Executables built with GraalVM native-image open the door to making Java build tools simpler and faster. Whether such a tool could match the speed of a warmed up Gradle build server is a question that would need answering, but it’d clearly be faster than Maven.