Building Native Images with Maven: An End-to-End Guide (original) (raw)

This guide walks you through integrating the Maven plugin for Native Image into your project. It starts from enabling the plugin, building the first native image, and running it.

Then it takes you to more advanced use-cases such as plugin’s configuration, applying optimizations, running native tests, and troubleshooting. If you are an advanced user, you can skip the getting started part and go directly to the advanced section.

Getting Started

To compile your application ahead of time with GraalVM Native Image and Maven, enable the Maven plugin for Native Image building. The plugin requires that you install GraalVM.

Add the Plugin

Add the plugin declaration to your pom.xml:

<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>compile-no-fork</goal>
      </goals>
      <phase>package</phase>
    </execution>
  </executions>
  <configuration>
      <mainClass>org.example.Main</mainClass>
  </configuration>
</plugin>

For convenience, you can create a Maven profile and add the plugin into it:

<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>compile-no-fork</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
          <configuration>
            <mainClass>org.example.Main</mainClass>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

Replace maven-plugin-version with the latest released version. All plugin versions are listed here.

The <mainClass> tag provides the path to the application main class (the main entry point). Adjust the path according to your application sources.

Build and Run Your Application

Once you registered the plugin, you can use the standard Maven phases. If you added the plugin inside the native profile, run your commands with the -Pnative option.

You can have multiple profiles, which is very convenient if you want to produce different versions of your native images for your application (optimized, static, and others). Continue to advanced use cases to learn more.

Advanced Use Cases: How to

For advanced use cases, this guide provides instructions for configuring the build process, running tests on native code, gathering execution profiles, troubleshooting missing configuration, and enabling diagnostic tools to analyze native images.

Configure Native Image Build

The plugin supports passing options directly to Native Image inside the <configuration> block. Using <buildArg>, you can pass any Native Image build option listed on this page.

The plugin also provides special properties to configure the build:

Here is an example of additional options usage:

<configuration>
  <mainClass>org.example.Main</mainClass>
  <imageName>myApp</imageName>
  <verbose>true</verbose>
  <buildArgs>
    <buildArg>-O3</buildArg> <!-- enables additional compiler optimizations -->
  </buildArgs>
  <environment>
    <variable1>value1</variable1>
    <variable2>value2</variable2>
  </environment>
  <jvmArgs>
    <arg>your-argument</arg>
  </jvmArgs>
</configuration>

| | As an alternative, you can pass additional build options via the NATIVE_IMAGE_OPTIONS environment variable, on the command line. This works similarly to JAVA_TOOL_OPTIONS, where the value of the environment variable is prefixed to the options supplied to native-image. | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Learn more about Native Image build configuration on the website.

Run Junit Tests

This plugin supports running tests on the JUnit Platform. The tests are compiled ahead of time and executed as native code.

<execution>
  <id>test-native</id>
  <goals>
    <goal>test</goal>
  </goals>
  <phase>test</phase>
</execution>
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.10.0</version>
  <scope>test</scope>
</dependency>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0</version>
</plugin>

The new execution you have just added, test-native, integrates with the Maven test phase. First, Maven runs the tests on the JVM, then compiles them ahead of time and executes them as native code.

Disable tests

If you wish to disable tests on the JVM as well as running native code tests, invoke Maven with the -DskipTests flag. This flag is supported by the Maven Surefire plugin and Native Build Tools.

./mvnw -Pnative -DskipTests package

If you wish to run tests on the JVM with the Maven Surefire plugin, but skip running tests as native code, invoke Maven with the -DskipNativeTests flag. This flag is specific to Native Build Tools.

./mvnw -Pnative -DskipNativeTests package

Alternatively, set <skipNativeTests> to true in the plugin configuration:

<configuration>
  <skipNativeTests>true</skipNativeTests>
</configuration>

This way you configure your Maven profile to skip generation and execution of tests as native code.

Gather Execution Profiles and Build Optimized Images

You may want to gather profiling information from your application’s execution to pinpoint areas of inefficiency. With this profiling data, you can also build an optimized native image.

The technique for building native images optimized on profiles is called Profile-Guided Optimization (PGO). With PGO you can “train” your native application for specific workloads to improve performance and throughput. The PGO workflow includes three steps.

| | PGO is available in Oracle GraalVM. | | -------------------------------------- |

Step 1: Build an instrumented native image by passing the --pgo-instrument option to native-image using <buildArg>. To prevent overwriting a previously built native executable, we recommend either creating a separate Maven profile for each build or specifying a unique file name using the <imageName> tag. For example:

<configuration>
  <imageName>instrumentedApp</imageName>
  <buildArgs>
      <buildArg>--pgo-instrument</buildArg>
  </buildArgs>
</configuration>

Run the build command:

Step 2: Gather profiles by running the instrumented executable. By default, the default.iprof file, if not specified otherwise, is generated alongside the native executable.

Step 3. Build an optimized native image with profiles by passing the --pgo option. You may want to provide a different name for the native image or create another Maven profile to handle this configuration:

<configuration>
  <imageName>optimizedApp</imageName>
  <buildArgs>
      <buildArg>--pgo</buildArg>
  </buildArgs>
</configuration>

Run the build command:

If the profile file has the default name and location, it will be automatically picked up. Alternatively, you can specify the file path as following: --pgo=myprofile.iprof.

If everything was done properly, you will see "PGO: user-provided" in the native image build output. Once the optimized image is built, run it: ./target/optimizedApp. The application’s performance when running from this native executable should be comparable to, or even faster than, running on the JVM. Learn more about PGO on the website.

Troubleshoot Missing Configuration

Detect Missing Metadata

Quite possibly, your application relies on external libraries. If your application uses a well-supported framework such as Spring or Micronaut, its dependencies should be compatible with Native Image. Frameworks and libraries that support Native Image by default provide configurations in the GraalVM Reachability Metadata Repository. When you build a native image, Native Build Tools reference this repository to apply the required configuration automatically.

| | You can find an extensive list of libraries and frameworks from the Java ecosystem tested with Native Image on this page. | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

However, it may happen, that your native image crashes at run time with a missing class or resource. To address this, start by checking if any required configuration is missing.

The best way to detect missing metadata is by running your native tests. Alternatively, you can identify missing configuration manually using the following method.

<configuration>
  <buildArgs>
    <buildArg>--exact-reachability-metadata</buildArg>
  </buildArgs>
</configuration>

| | The --exact-reachability-metadata option was introduced in GraalVM for JDK 23. With older versions, use -H:ThrowMissingRegistrationErrors= instead. | | --------------------------------------------------------------------------------------------------------------------------------------------------------- |

./target/myApp -XX:MissingRegistrationReportingMode=Warn

| | With GraalVM versions older than JDK 23, pass -H:MissingRegistrationReportingMode=Warn at build time instead. | | ----------------------------------------------------------------------------------------------------------------- |

Collect Metadata Automatically with Tracing Agent

Your application may use dynamic Java features such as reflection, serialization, or resource loading. It is also possible that a framework your application relies on uses a library dependency incompatible with Native Image. In such cases, additional metadata is required.

The easiest way to collect the missing metadata is by using the Tracing Agent. This agent tracks all usages of dynamic features during application execution on the JVM and generates the necessary configuration.

| | This guide demonstrates how to generate metadata from your tests. Generating metadata from your main application requires more configuration. The process is otherwise identical, except that you use the package phase instead of the test phase. | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

The agent is disabled by default. You can enable it on the command line or in pom.xml.

To enable the agent via the command line, pass the -Dagent=true option when running Maven:

./mvnw -Pnative -Dagent=true test

| | Enabling the agent via the command line only attaches it for a specific run; it does not automatically run every time you build the application. | | --------------------------------------------------------------------------------------------------------------------------------------------------- |

To enable the agent in pom.xml and collect missing metadata, do the following.

Step 1: Enable the agent by setting <agent> to true in the native profile:

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

From that point on, commands you execute will run with the agent attached.

Step 2: Copy the generated metadata from the default location, target/native/agent-output, to the resources directory, for example, resources/META-INF/native-image. Native Image automatically uses the metadata from this location.

To do that with Maven, configure and run the metadataCopy task.

Add a new task named metadataCopy inside the agent block that you added in step 1. Your agent configuration should look like this:

<agent>
  <enabled>true</enabled>
  <metadataCopy>
    <disabledStages>
      <stage>main</stage>
    </disabledStages>
    <merge>true</merge>
    <outputDirectory>src/test/resources/META-INF/native-image</outputDirectory>
  </metadataCopy>
</agent>

In this block:

Step 3: Now that the metadataCopy task is configured, run the agent to collect the metadata and copy it to the other location:

./mvnw -Pnative test native:metadata-copy

Step 4: Finally, proceed without the agent and build the native image with the metadata. From that point on, you can run your tests with:

Learn more about how to fine-tune the agent further here.

Use Diagnostics Tools

If you need to diagnose the native applications you build, or monitor your Java application when launched from a native executable, Native Image offers tools for debugging and analyzing the produced binary. For example:

<configuration>
  <debug>true</debug>
  <buildArgs>
    <buildArg>--emit build-report</buildArg>
    <buildArg>--enable-monitoring=jfr</buildArg>
  </buildArgs>
</configuration>

| | Build Report is available in Oracle GraalVM. When running on GraalVM for JDK 21, pass the -H:+BuildReport option instead to generate a build report. | | -------------------------------------------------------------------------------------------------------------------------------------------------------- |

All the monitoring and debugging tools listed on the website, can be enabled in the plugin configuration using <buildArgs>.

You will find the output of these tools among the generated artifacts after running:

Learn more