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 theapplication.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 thenative-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 |
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 thenative-image
tool insidegraalvmNative
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 |
-
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 |
-
The
--enable-monitoring=jfr
instructs the plugin to build a native executable with the JDK Flight Recorder (JFR) support. -
The
<debug>
option generates a native executable with debug information for source-level debugging with the GNU Debugger (GDB).
All the monitoring and debugging tools listed on the website, can be enabled in the plugin configuration using buildArgs
.
Learn more
To continue learning, refer to the extensive reference documentation for the GraalVM Native Image Gradle plugin.