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.

  • Build a native executable of your application. This command will compile your application and create a native executable in the target/ directory:

./mvnw -Pnative package
  • Run the application from the native executable:

./target/myApp

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:

  • <environment> - Sets the environment options

  • <imageName> - Specifies of the name for the native executable file. If a custom name is not supplied, the artifact ID of the project will be used by default (defaults to the project name).

  • <jvmArgs> - Passes the given argument directly to the JVM running the native-image tool

  • <quickBuild> - Enables quick build mode

  • <verbose> - Enables the verbose output

  • and many more listed here.

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.

  • To execute tests, add one more <execution> to the plugin declaration:

<execution>
  <id>test-native</id>
  <goals>
    <goal>test</goal>
  </goals>
  <phase>test</phase>
</execution>
  • Add the JUnit 5 dependency to pom.xml to include the testing framework. It will only be used during the test phase and not included in the final build artifact:

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.10.0</version>
  <scope>test</scope>
</dependency>
  • Add the Maven Surefire Plugin into the plugins section of the native profile:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0</version>
</plugin>
  • Run the tests:

./mvnw -Pnative test

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:

./mvnw -Pnative package

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.

./target/instrumentedApp

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:

./mvnw -Pnative package

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.

  • Pass the --exact-reachability-metadata option to the native-image tool in pom.xml, as shown below:

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

  • Rebuild the application:

./mvnw -Pnative package
  • Run the application from the native executable with the -XX:MissingRegistrationReportingMode=Warn option:

./target/myApp -XX:MissingRegistrationReportingMode=Warn

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

  • If there is any missing metadata printed to the console, add it to the configuration file manually, as described here, or collect it automatically using the Tracing agent. (See next.)

  • Rebuild your native image and test again.

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:

  • <outputDirectory> specifies location where you want to copy the generated metadata.

  • <disableStages> - you can disable metadata copy for a concrete Maven phase. In this you do not want the agent output from the main phase.

  • <merge> - specifies whether the metadata you want to copy, should be merged with the metadata that already exists in the given location, or not. This only makes sense when there is already some existing metadata, created before.

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:

./mvnw -Pnative test

If your native image is successfully build, but still fails at run time, check the troubleshooting guide Troubleshoot Native Image Run-Time Errors.

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>
  • The --emit build-report option generates an HTML page report alongside the native executable that you can open in a browser. It provides broad information about each build stage as well as the generated binary’s contents. You can read more about Build Report features here.

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:

./mvnw -Pnative package