badge

Introduction

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

For upgrading please take a look at the Changelog.

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 to register the native-maven-plugin.

  <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 fail 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.

Testing pre-releases

You can use 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 configuration options are available.

<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>

<imageName>

Use <imageName> to set a custom filename for the generated native image. If a custom image name is not supplied, the artifact ID of the project will be used by default.

<buildArgs>

If you want to pass additional arguments to the native image builder, use <buildArgs> in the configuration of the plugin.

<skipNativeBuild>

To skip generation of the native image, supply <skipNativeBuild>true</skipNativeBuild> in the configuration of the plugin.

<agent>

Configuration of the native agent. See Enabling the agent and Configuring agent options for details.

For example, to build a native image named executable-name that uses org.example.ClassName as its main class with assertions enabled, add the following <configuration> block for the native-maven-plugin.

<configuration>
  <imageName>executable-name</imageName>
  <mainClass>org.example.ClassName</mainClass>
  <buildArgs>
    <buildArg>--no-fallback</buildArg>
  </buildArgs>
</configuration>
If you use GraalVM Enterprise as the JAVA_HOME environment, the plugin builds a native image with enterprise features enabled — for example, 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 POMs. Suppose you have 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

Testing support

This plugin supports running tests on the JUnit Platform as native images. This means that tests will be compiled and executed as native code.

In theory, any TestEngine supported on the JUnit Platform should be supported by this plugin as long as the programming language used by the TestEngine and the programming language used to write the tests is supported in a GraalVM native image. This plugin provides explicit support for the JUnit Jupiter and JUnit Vintage test engines, and support for additional test engines should be possible with custom native configuration.

In order to use the recommended JUnit Platform test listener mode, you need to enable extensions for the native-maven-plugin by adding <extensions>true</extensions> as follows.

<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.

This plugin requires JUnit Platform 1.8 or higher.
This plugin provides integration with Maven Surefire 2.22.0 or higher.

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, classpath resources, dynamic proxies or other features requiring explicit native configuration, it may prove helpful to first run your application or tests using the native-image-agent.

The Native Image 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).

The agent generates the native configuration files in a subdirectory of target/native/agent-output. Although those files will be automatically used if you run your build with the agent enabled, you should consider reviewing the generated files and adding them to your sources instead.

Enabling the agent

The agent is disabled by default, but it can be enabled within your pom.xml file or via the command line.

To enable the agent by default, specify <enabled>true</enabled> as follows in the configuration of the native-maven-plugin in your POM.

<configuration>
  <agent>
    <enabled>true</enabled>
  </agent>
</configuration>

To enable the agent via the command line, supply the -Dagent=true flag when running Maven. The examples in the following sections demonstrate how to do this for your application and for tests.

If you have enabled the agent within your POM, you can still disable it via the command line by supplying the -Dagent=false flag.

Configuring agent options

If you would like to configure the options for the agent — for example, to configure experimental features such as experimental-class-loader-support or advanced features such as Caller-based Filters and Access Filters — you can include <options> within the <agent> block of the configuration of the native-maven-plugin in your POM.

  • You can supply multiple sets of <options>.

  • You can declare an unnamed <options> element which will always be used whenever the agent is enabled. This should be used to declare common options that will be used for all executions with the agent.

  • Additional <options> elements must declare a unique name attribute.

    • To configure options for your application, use the name main.

    • To configure options for your tests, use the name test.

    • To configure additional sets of options, declare each with a unique name other than main or test.

  • The main options are enabled automatically whenever your application is run with the agent.

  • The test options are enabled automatically whenever your tests are run with the agent.

  • To enable any other set of named <options>, supply -DagentOptions=<NAME> as a command-line argument for Maven, where <NAME> corresponds to the name attribute of the <options> element.

The Native Image Maven plugin automatically configures the config-output-dir for the agent. An attempt to configure a custom value for the config-output-dir option will therefore result in a build failure.

The following example is likely more complex than anything you would do in your own projects, but it demonstrates how to configure four sets of <options>.

  • The unnamed set is always active.

  • The main set is automatically active for application execution.

  • The test set is automatically active for test execution.

  • The periodic-config set is never active by default, but it can be enabled via -DagentOptions=periodic-config on the command line.

<configuration>
    <agent>
        <options><!-- shared options -->
            <option>experimental-class-loader-support</option>
        </options>
        <options name="main"><!-- main options -->
            <option>access-filter-file=${basedir}/src/main/resources/access-filter.json</option>
        </options>
        <options name="test"><!-- test options -->
            <option>access-filter-file=${basedir}/src/test/resources/access-filter.json</option>
        </options>
        <options name="periodic-config"><!-- periodic-config options -->
            <option>config-write-period-secs=30</option>
            <option>config-write-initial-delay-secs=5</option>
        </options>
    </agent>
</configuration>

Running tests with the agent

The simplest way to use the agent is to do it via execution of your tests.

Run your test suite with:

mvn -Pnative -Dagent=true test

When the agent system property is set to true (or when the agent is enabled in the POM), the agent will be automatically attached to your Maven Surefire test execution, and the generated files can be found in the target/native/agent-output/test directory.

To run your tests with custom agent options, supply the -DagentOptions=<NAME> command-line argument to Maven as follows. See the documentation for agent options for details.

mvn -Pnative -Dagent=true -DagentOptions=periodic-config test
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

To execute your application with custom agent options, supply the -DagentOptions=<NAME> command-line argument to Maven as follows. See the documentation for agent options for details.

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

Both of the above commands will generate configuration files in the target/native/agent-output/main directory. If you want to run your native application with those configuration files, you then need to execute the following command:

mvn -Pnative -Dagent=true -DskipTests=true package exec:exec@native
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.