Simplifying native-image generation with Maven plugin and embeddable configuration

Paul Wögerer
graalvm
Published in
6 min readMar 19, 2019

In this blog post we show two features recently added to GraalVM to simplify the generation of native images. One is a Maven plugin so you can include the native image generation in your build without calling the command line utilities manually. Then we look at native-image.properties as a way to include the configuration for your library in the jar file to avoid manual configuration.

Building netty-plot with native-image-maven-plugin

In a previous blog post we have shown how to make use of isolates and compressed references in GraalVM native images. Maven was used to build a uber-jar (fat-jar) that was then compiled into a native image with the GraalVM native-image tool. As of recent GraalVM releases we are now able to build native images right out of maven without running the native-image tool as a separate step after building the uber-jar.

This blog post shows you how to use Maven to easily build native images. If you are using GraalVM as your JAVA_HOME it is sufficient to just add:

native-image-maven-plugin pom.xml snippet

into the <plugins> section of your pom-file. For the previously published native-netty-plot example the above is sufficient to allow maven to build the native-image for us. Running mvn package will now give us:

`mvn package` output of native-image-maven-plugin

The output shows that the plugin figured out what jar-files it needs to pass to native-image and what the executable main-class should be (com.oracle.svm.nettyplot.PlotServer). Once mvn package completes, you can find the executable netty-plot in the target directory of the maven project.

Since the Maven integration is available in almost any IDE you having a maven plugin means you can generate native images without leaving it:

Generating native images without leaving your IDE with the maven plugin

Plugin customization

It is possible to customize the image build with a <configuration> node in the <plugin> node. Currently the following options are available.

Configuration parameter <mainClass>

By default the plugin consults several locations in your pom file in the following order to determine what the main class of the image should be:

  • <maven-shade-plugin> <transformers> <transformer> <mainClass>
  • <maven-assembly-plugin> <archive> <manifest> <mainClass>
  • <maven-jar-plugin> <archive> <manifest> <mainClass>

If it cannot find a proper definition of a main class in any of these locations you can add a <mainClass> in the <configuration>node of the plugin to specify a custom main class.

Configuration parameter <imageName>

If an image filename is not set explicitly native-image will choose one for you (e.g. netty-plotin netty-plot example above). Use parameter <imageName>if you want to provide a custom filename for your image.

Configuration parameter <buildArgs>

If you want to pass additional native-image options for image building you can use the <buildArgs> parameter.

Example configuration

Suppose you want to build an image with assertions enabled that uses com.acme.MeepMeep as main class. You would simply add:

<configuration> node with <buildArgs>

to the definition of the native-image-maven-plugin in your pom-file.

Using maven-plugin with GraalVM Enterprise Edition:

If you use GraalVM Enterprise Edition as your JAVA_HOME the plugin builds images with enterprise feature enabled. E.g. the executable will automatically be built with compressed references and other optimizations enabled.

Using maven-plugin without GraalVM:

With the plugin it is also possible to build images even without having a GraalVM release installed on your system. The only requirement is to have a JVMCI enabled JDK on your machine. Currently image building is only supported for Java 8 (we are actively working on adding support for Java 11) therefore — as of now — you have to download a JVMCI enabled OpenJDK8. Once you have such a JDK set as your JAVA_HOME you can use the above native-image-maven-plugin snippet in your maven projects and image building will work. The reason this works is because the plugin will cause maven to download all the necessary components for image building directly from maven-central. In this scheme most of the time updating to a later version is simply a matter of changing the <version> tag in your <plugin> definition. For example, once we have 1.0-RC15 released you just need to update your plugin definition to:

Updating native-image-maven-plugin to new GraalVM release

No further steps are required. Maven will take care of the rest!

⚠ Please note that in this scenario using macro options like--language:<name>or --tool:<name>will not work as they require a full GraalVM release to be used as your JAVA_HOME. Also be aware that if a newer GraalVM release has changes that depend on a newer JVMCI enabled JDK you will have to update the JVMCI enabled JDK as well … caveat emptor.

Composable native-image.properties

Since the recent releases of GraalVM we also have another important native-image building improvement. It is now possible to embed native-image.properties files in META-INF/native-imagein jar-files. The native-image tool will automatically process all files it finds in this resource location and use them to construct native-image command line arguments. The native-netty-plot example from the previous blog post already makes use of this feature. By having a native-image.properties file located at src/main/resources/META-INF/native-image/com.oracle.substratevm/netty-plot building the image now does not require any additional arguments. Running the following command:

native-image -jar target/netty-plot-0.1-jar-with-dependencies.jar

is sufficient to build the image. Before, building an image that contains Netty always required the use of additional command line options. Usually it was something like:

-H:ReflectionConfigurationResources=reflection-config.json \
--delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder

The reason this works now is that these extra flags can now be embedded in the jar-file itself. Looking into the native-image.properties file from above shows what extra flags are passed to native-image when netty-plot-0.1-jar-with-dependencies.jar is on the image classpath.

META-INF/native-image/com.oracle.substratevm/netty-plot/native-image.properties

Here again we find among other things the familiar options needed for building images that contain Netty. Notice the use of ${.} in the above file. This is a convenient feature of native-image.properties files that allows to refer to the location the native-image.properties file resides in. This way it is possible to conveniently specify resource locations relative to the location of a native-image.properties file. In the above case ${.} gets expanded to META-INF/native-image/com.oracle.substratevm/netty-plot.

Making Java libraries native-image compatible by default

If correctly applied, the mechanism described above allow Java library writers to equip their libraries with out-of-the-box native-image compatibility.

In the example of Netty, if we had an artifact io.netty:netty-allthat contains:

META-INF/native-image/io.netty/netty-all/reflection-config.json
META-INF/native-image/io.netty/netty-all/native-image.properties

in addition to the substitutions from com.oracle.svm.nettyplot.NettySubstitutionsthen Netty could be used with native-image out of the box. The other native-image.properties file for our netty-plot example would then only contain options that netty-plot itself needs:

netty-plot native-image.properties after removing Netty specific bits

In addition to that, we could get rid of the com.oracle.substratevm:svm dependency in our pom file

Netty-specific native-image dependency in netty-plot pom.xml

which is currently only needed because NettySubstitutions depend on it.

💡 About <scope>provided</scope>

The <scope>provided</scope> used above is important because the substitutions are only needed during image building where com.oracle.substratevm:svm is provided by the image builder itself. If this scope definition is omitted Maven would put com.oracle.substratevm:svm and all its transitive dependencies into the jar file when it creates an uber-jar. Clearly we wouldn't want that to happen!

If io.netty:netty-all would contain all the specifics needed to allow using it in native-images the above dependency in netty-plot could be reduced to

netty-plot depends on org.graalvm.nativeimage API

because netty-plot itself only depends on our official org.graalvm.nativeimage API.

Conclusion

In this article we looked at how one can use the GraalVM native image Maven plugin to simplify the creation of native images from your projects. We also looked at a way for libraries to embed the necessary native-image configuration bits in the META-INF/native-image directory in the jar files.

These two are a quality of life improvements that allow the ecosystem to take advantage of the GraalVM native images and ahead of time compilation. Download GraalVM and give it a try: https://www.graalvm.org/downloads.

See a good feature missing or have any other feedback? Please reach out to us, leave an issue at the GraalVM GitHub repo or contact us any other way!

--

--

Paul Wögerer
graalvm
Editor for

Researcher at Oracle Labs. Working on GraalVM native image generation (Substrate VM). Opinions are my own.