badge

Introduction

The Maven plugin for GraalVM Native Image building adds support for building and testing native images using Apache Maven™.

Quickstart

This plugin first requires that you setup GraalVM and native-image properly. It will then make use of Maven profiles to enable building and testing of native images.

Configuration

Registering the plugin

Add the following profile to your pom.xml file:

  <profiles>
    <profile>
      <id>native</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <version>${native.maven.plugin.version}</version>
            <extensions>true</extensions>
            <executions>
              <execution>
                <id>build-native</id>
                <goals>
                  <goal>build</goal>
                </goals>
                <phase>package</phase>
              </execution>
              <execution>
                <id>test-native</id>
                <goals>
                  <goal>test</goal>
                </goals>
                <phase>test</phase>
              </execution>
            </executions>
            <configuration>
              <!-- ... -->
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

You can then build a native executable directly with Maven using the mvn -Pnative -DskipTests package command without running the native-image command as a separate step.

The plugin figures out which JAR files it needs to pass to the native image and what the executable main class should be. If the heuristics fails with the no main manifest attribute, in target/<name>.jar error, the main class should be specified in the <configuration> node of the plugin. When mvn -Pnative package completes, an executable is ready for use, generated in the target directory of the project.

Tip
Testing pre-releases

You can use the development versions of the plugin by adding our snapshot repository. Pre-releases are provided for convenience, without any guarantee.

<pluginRepositories>
    <pluginRepository>
        <id>graalvm-native-build-tools-snapshots</id>
        <name>GraalVM native-build-tools Snapshots</name>
        <url>https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots</url>
        <releases>
            <enabled>false</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </pluginRepository>
</pluginRepositories>

Configuration options

If you use Native Image Maven plugin, it will pick up all the configuration for your application stored below the META-INF/native-image/ resource location, as described in Native Image Build Configuration. It is also possible to customize the plugin within a <configuration> node. The following configurations are available.

  1. Configuration parameter <mainClass>: if the execution fails with the no main manifest attribute, in target/<name>.jar error, the main class should be specified. By default the plugin consults several locations in the pom.xml 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>

  2. Configuration parameter <imageName>: if an image filename is not set explicitly, use parameter <imageName> to provide a custom filename for the image.

  3. Configuration parameter <buildArgs>: if you want to pass additional options for to the native image builder, use the <buildArgs> parameter in the definition of the plugin. For example, to build a native image with assertions enabled that uses com.test.classname as a main class, add:

<configuration>
  <imageName>executable-name</imageName>
  <mainClass>com.test.classname</mainClass>
  <buildArgs>
    <buildArg>--no-fallback</buildArg>
  </buildArgs>
  <skipNativeBuild>false</skipNativeBuild>
</configuration>

If you use GraalVM Enterprise as the JAVA_HOME environment, the plugin builds a native image with Enterprise features enabled, e.g., an executable will automatically be built with compressed references and other optimizations enabled.

Reusing configuration from a parent POM

The <buildArgs> element can be combined between parent and children POM. Suppose the following parent POM definition:

<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <version>${current_plugin_version}</version>
  <configuration>
    <imageName>${project.artifactId}</imageName>
    <mainClass>${exec.mainClass}</mainClass>
    <buildArgs>
      <buildArg>--no-fallback<buildArg>
    </buildArgs>
  </configuration>
</plugin>

Children projects have the ability to append <buildArg> arguments in the following way:

<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <configuration>
    <buildArgs combine.children="append">
      <buildArg>--verbose</buildArg>
    </buildArgs>
  </configuration>
</plugin>

In this case, the arguments that will be passed to the native-image executable will be:

--no-fallback --verbose

JUnit Testing support

In order to use the recommended test listener mode, you need to enable the plugin extensions:

In addition, you need to configure the surefire plugin:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>${native.maven.plugin.version}</version>
    <extensions>true</extensions>

Running mvn -Pnative test will then build and run native tests.

Long classpath and shading support

Under Windows, it is possible that the length of the classpath exceeds what the operating system supports when invoking the CLI to build a native image.

If this happens, one option is to use a shaded jar and use it instead of individual jars on classpath.

First, you’ll need to setup the Maven Shade plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.4</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <shadedArtifactAttached>true</shadedArtifactAttached>
            </configuration>
        </execution>
    </executions>
</plugin>

If you need testing support, add the JUnit Platform Native dependency explicitly:

<dependencies>
    <dependency>
        <groupId>org.graalvm.buildtools</groupId>
        <artifactId>junit-platform-native</artifactId>
        <version>${junit.platform.native.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Then the native plugin needs to be configured to use this jar instead of the full classpath:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>${native.maven.plugin.version}</version>
    <extensions>true</extensions>
    <executions>
        <execution>
            <id>build-native</id>
            <goals>
                <goal>build</goal>
            </goals>
            <phase>package</phase>
        </execution>
    </executions>
    <configuration>
        <skip>false</skip>
        <imageName>${imageName}</imageName>
        <buildArgs>
            <buildArg>--no-fallback</buildArg>
        </buildArgs>
        <classpath>
            <param>${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar</param>
        </classpath>
    </configuration>
</plugin>

Depending on the other plugins your build uses (typically the Spring Boot plugin), you might have to configure, in addition, the main class:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
	<artifactId>native-maven-plugin</artifactId>
	<version>${native.buildtools.version}</version>
	<configuration>
		<imageName>${project.artifactId}</imageName>
		<mainClass>${exec.mainClass}</mainClass>
		<buildArgs>
			<buildArg>--no-fallback</buildArg>
		</buildArgs>
		<classpath>
			<param>
				${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar
			</param>
		</classpath>
...

To be able to execute tests in native mode, you will need more setup:

  • Create a src/assembly/test-jar-with-dependencies.xml file with the following contents:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>tests</id>
    <formats>
        <format>jar</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/test-classes</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>${project.build.outputDirectory}</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>test</scope>
        </dependencySet>
    </dependencySets>
</assembly>
  • Add the assembly plugin to your native profile:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.4.1</version>
    <configuration>
        <descriptors>
            <descriptor>src/assembly/test-jar-with-dependencies.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-test-jar</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  • Due to a limitation in Maven, you will need to move the tests execution to the "integration-test" phase:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M5</version>
</plugin>
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>${native.maven.plugin.version}</version>
    <extensions>true</extensions>
    <executions>
        <execution>
            <id>test-native</id>
            <goals>
                <goal>test</goal>
            </goals>
            <phase>integration-test</phase>
            <configuration>
                <classpath>
                    <param>${project.build.directory}/${project.artifactId}-${project.version}-tests.jar</param>
                </classpath>
            </configuration>
        </execution>
        <execution>
            <id>build-native</id>
            <goals>
                <goal>build</goal>
            </goals>
            <phase>package</phase>
            <configuration>
                <classpath>
                    <param>${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar</param>
                </classpath>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <skip>false</skip>
        <imageName>${imageName}</imageName>
        <buildArgs>
            <buildArg>--no-fallback</buildArg>
        </buildArgs>
    </configuration>
</plugin>

Finally, you will need to execute tests using the integration-test phase instead of test:

./mvn -Pnative integration-test

Please refer to the Maven Shade plugin documentation for more details on how to configure shading and the Maven Assembly plugin documentation to tweak what to include in tests.

Reflection support and running with the native agent

If your project requires reflection, then native-image-agent run might be necessary.

The Maven plugin simplifies generation of the required configuration files by injecting the agent automatically for you (this includes, but is not limited to the reflection file).

Automation will generate configuration files in target/native/agent-output. While those files will be automatically used if you run your build with the -Dagent=true flag, you should consider reviewing them and add them to your sources instead.

Running tests with the agent

The simplest way to execute the agent is to do it via execution of unit tests. Run your test suite with:

mvn -Pnative -Dagent=true test

Then the agent will be automatically attached to your tests execution. The generated files will be found in target/native/agent-output/test.

Warning
If the agent is enabled, the --allow-incomplete-classpath option is automatically added to your native build options.

Running your application with the agent

Executing your application with the agent is more involved and requires you to configure a separate mojo execution which allows forking the Java process.

In your native Maven profile section, add the following:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>java-agent</id>
            <goals>
                <goal>exec</goal>
            </goals>
            <configuration>
                <executable>java</executable>
                <workingDirectory>${project.build.directory}</workingDirectory>
                <arguments>
                    <argument>-classpath</argument>
                    <classpath/>
                    <argument>${mainClass}</argument>
                </arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

Then you can execute your application with the agent by running:

mvn -Pnative -Dagent=true -DskipTests=true -DskipNativeBuild=true package exec:exec@java-agent

This will generate configuration files at target/native/agent-output/exec. If you want to run your native application with those configuration files, you then need to execute this command line:

mvn -Pnative -Dagent=true -DskipTests=true package exec:exec@native
Warning
If the agent is enabled, the --allow-incomplete-classpath option is automatically added to your native build options.

Javadocs

In addition, you can consult the Javadocs of the plugin.