This guide walks you through integrating the Gradle 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 Gradle, enable the Gradle plugin for Native Image building. The plugin requires that you install GraalVM.

A JDK version between 17 and 21 is required to execute Gradle (see the Gradle Compatibility Matrix).

Add the Plugin

  • Add the plugin declaration to your build.gradle / build.gradle.kts file inside the plugins block:

  id 'org.graalvm.buildtools.native' version '0.10.5'
  id("org.graalvm.buildtools.native") version "0.10.5"

All plugin versions are listed here

Build and Run Your Application

This plugin works with the application plugin and registers a number of tasks and extensions for you to configure.

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

./gradlew nativeCompile
  • Run the application from the native executable:

./gradlew nativeRun

Continue reading below to learn more about the plugin.

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 graalvmNative block in the build.gradle / build.gradle.kts file. Using buildArgs.add("<option>"), you can pass any Native Image build option listed on this page. You can pass options to configure the main or the test native binary, or both at the same time.

The plugin also provides special properties to configure the build:

  • mainClass - Provides the main class to use, defaults to the application.mainClass

  • imageName - Specifies 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:

graalvmNative {
    binaries.all {
        // common options
        verbose = true
    }

    binaries.main {
        // options to configure the main binary
        imageName = 'myApp'
        mainClass = 'org.example.Main'
        buildArgs.add('-O3') // enables additional compiler optimizations
    }

    binaries.test {
        // options to configure the test binary
        quickBuild = true
        debug = true
    }
}
graalvmNative {
    binaries.all {
        // common options
        verbose.set(true)
    }

    binaries.main {
        // options to configure the main binary
        imageName.set('application')
        mainClass.set('org.example.Main')
        buildArgs.add('-O3')  // enables additional compiler optimizations
    }

    binaries.test {
        // options to configure the test binary
        quickBuild.set(true)
        debug.set(true)
    }
}

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.

  • Add the JUnit 5 dependency to build.gradle / build.gradle.kts to include the testing framework:

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'junit:junit:4.13.2'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.1')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.1')
testImplementation('junit:junit:4.13.2')
  • Run the tests:

./gradlew nativeTest

The tests are compiled ahead of time and executed 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 directly to the compile command:

./gradlew nativeCompile --pgo-instrument

This generates a native executable under build/native/nativeCompile with the -instrumented suffix.

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.

./build/native/nativeCompile/application-instrumented

Step 3. Build an optimized native image with profiles. This step involves copying the generated profile to the conventional directory, ensuring it is automatically used during the build process. Place the default.iprof file in the src/pgo-profiles/main directory, and then run:

./gradlew nativeCompile

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:

./gradlew nativeRun

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 inside graalvmNative block of your build.gradle, as shown below:

graalvmNative {
    binaries.all {
        buildArgs.add('--exact-reachability-metadata')
        runtimeArgs.add('-XX:MissingRegistrationReportingMode=Warn')
    }
}
graalvmNative {
    binaries.all {
        buildArgs.add('--exact-reachability-metadata')
        runtimeArgs.add('-XX:MissingRegistrationReportingMode=Warn')
    }
}

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

  • Rebuild and re-run the application:

./gradlew nativeRun
  • 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 this 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.

The agent is disabled by default. You can enable it on the command line or inside the graalvmNative block in build.gradle / build.gradle.kts.

To enable the agent via the command line, pass the -Pagent option when running Gradle:

./gradlew -Pagent run

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 inside the build configuration and collect missing metadata, do the following.

Step 1: Enable the agent by setting agent to true in the graalvmNative block:

graalvmNative {
    agent {
        enabled = true
    }
}
graalvmNative {
    agent {
        enabled.set(true)
    }
}

From that point on, commands such as run or test will execute with the agent attached. For example you can execute the following command and the agent will be attached automatically:

./gradlew run

By default, the agent creates the metadata in the build/native/agent-output directory.

Step 2: Copy the generated metadata from the default location, build/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 Gradle, configure and run the metadataCopy task.

Add a new task named metadataCopy inside the graalvmNative block. Your agent configuration should look like this:

agent {
    enabled = true
    metadataCopy {
        inputTaskNames.add("run")
        outputDirectories.add("src/main/resources/META-INF/native-image/org.example")
        mergeWithExisting = true
    }
}
agent {
    enabled.set(true)
    metadataCopy {
        inputTaskNames.add("run")
        outputDirectories.add("src/main/resources/META-INF/native-image/org.example")
        mergeWithExisting.set(true)
    }
}

In this block:

  • inputTaskNames - specifies tasks previously executed with the agent attached (tasks that generated metadata in the last step)

  • outputDirectories - specifies the location where you want to copy the generated metadata

  • mergeWithExisting - specifies whether the metadata you want to copy should be merged with the metadata that already exists on the give 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 metadataCopy task:

./gradlew metadataCopy

Step 4: Finally, build the native image with the metadata:

./gradlew nativeCompile

Run it:

./gradlew nativeRun

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:

graalvmNative {
    binaries.all {
        buildArgs.add('--emit build-report')
        buildArgs.add('--enable-monitoring=jfr')
        debug = true
    }
}
graalvmNative {
    binaries.all {
        buildArgs.add('--emit build-report')
        buildArgs.add('--enable-monitoring=jfr')
        debug.set(true)
    }
}
  • 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.