Introduction
The Gradle plugin for GraalVM Native Image building adds support for building and testing native images using the Gradle build tool.
For upgrading please take a look at the Changelog.
Quickstart
You can find full samples in the source repository.
The plugin requires that you setup GraalVM.
The easiest way to install GraalVM is to use the SDKMAN!.
For other installation options, go to GraalVM Downloads.
Reference documentation
Adding the plugin
Add following to plugins
section of your project’s build.gradle
/ build.gradle.kts
:
plugins {
// ...
// Apply GraalVM Native Image plugin
id 'org.graalvm.buildtools.native' version '0.10.0'
}
plugins {
// ...
// Apply GraalVM Native Image plugin
id("org.graalvm.buildtools.native") version "0.10.0"
}
This plugin supplements and heavily relies on regular Java plugins (e.g. application , java-library , java etc). Not having them included in your project will most probably cause errors.
|
Testing pre-releases
You can use development versions of the plugin by adding our snapshot repository instead. Pre-releases are provided for convenience, without any guarantee.
|
Installing GraalVM Native Image tool
By default, the plugin will try to use the native-image
tool that is bundled with the JDK that is used to run Gradle.
This means you must make sure that you run Gradle with a GraalVM JDK.
Alternatively, you may choose to:
-
Set up a
GRAALVM_HOME
environment variable pointing to your GraalVM installation, in which case the JDK pointed at this location will be used for Native Image builds instead
Configuration
This plugin works with the application
plugin and will register a number of tasks and extensions for you to configure.
Available tasks
The main tasks that you will want to execute are:
-
nativeCompile
, which will trigger the generation of a native executable of your application -
nativeRun
, which executes the generated native executable -
nativeTestCompile
, which will build a native image with tests found in thetest
source set -
nativeTest
, which will execute tests found in thetest
source set in native mode
Those tasks are configured with reasonable defaults using the graalvmNative
extension binaries
container of type NativeImageOptions.
The main executable is configured by the image named main
, while the test executable is configured via the image named test
.
Native image options
The NativeImageOptions allows you to tweak how the native image is going to be built. The plugin allows configuring the final binary, the tests one, as well as apply options to both.
graalvmNative {
binaries {
main {
imageName = "my-app"
mainClass = "org.jackup.Runner"
buildArgs.add("-O4")
}
test {
buildArgs.add("-O0")
}
}
binaries.all {
buildArgs.add("--verbose")
}
}
graalvmNative {
binaries {
named("main") {
imageName.set("my-app")
mainClass.set("org.jackup.Runner")
buildArgs.add("-O4")
}
named("test") {
buildArgs.add("-O0")
}
}
binaries.all {
buildArgs.add("--verbose")
}
}
Using Gradle toolchains
Enabling toolchain detection
Instead of relying on the JDK which is used to run Gradle, you can use the Gradle toolchain support to select a specific GraalVM installation.
However, because of limitations in Gradle, the plugin may not be able to properly detect the toolchain. In particular, this will only work properly if you only have GraalVM JDKs installed on the machine: Otherwise, Gradle will not be able to reliably detect GraalVM JDKs, nor detect GraalVM distributions from different vendors.
Should you still want to enable toolchain support, you do it via the graalvmNative
extension:
graalvmNative {
toolchainDetection = true
}
graalvmNative {
toolchainDetection.set(true)
}
Selecting the GraalVM toolchain
By default, the plugin will select a Java 11 GraalVM toolchain using the vendor string GraalVM
,
which works properly for GraalVM up to version 22.3 included.
More recent versions of GraalVM do not have a specific version and are aligned with the language version they support.
If you want to use a different toolchain, for example a distribution compatible with Java 20 from Oracle, you can configure the toolchain like this:
graalvmNative {
binaries {
main {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(20)
vendor = JvmVendorSpec.matching("Oracle Corporation")
}
}
}
}
graalvmNative {
binaries {
named("main") {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(20))
vendor.set(JvmVendorSpec.matching("Oracle Corporation"))
})
}
}
}
Again, be aware that the toolchain detection cannot distinguish between GraalVM JDKs and standard JDKs without Native Image support: if you have both installed on the machine, Gradle may randomly pick one or the other.
Configuration options
The following configuration options are available for building images:
graalvmNative {
agent {
defaultMode = "standard" // Default agent mode if one isn't specified using `-Pagent=mode_name`
enabled = true // Enables the agent
modes {
// The standard agent mode generates metadata without conditions.
standard {
}
// The conditional agent mode generates metadata with conditions.
conditional {
userCodeFilterPath = "path-to-filter.json" // Path to a filter file that determines classes which will be used in the metadata conditions.
extraFilterPath = "path-to-another-filter.json" // Optional, extra filter used to further filter the collected metadata.
}
// The direct agent mode allows users to directly pass options to the agent.
direct {
// {output_dir} is a special string expanded by the plugin to where the agent files would usually be output.
options.add("config-output-dir={output_dir}")
options.add("experimental-configuration-with-origins")
}
}
callerFilterFiles.from("filter.json")
accessFilterFiles.from("filter.json")
builtinCallerFilter = true
builtinHeuristicFilter = true
enableExperimentalPredefinedClasses = false
enableExperimentalUnsafeAllocationTracing = false
trackReflectionMetadata = true
// Copies metadata collected from tasks into the specified directories.
metadataCopy {
inputTaskNames.add("test") // Tasks previously executed with the agent attached.
outputDirectories.add("src/main/resources/META-INF/native-image")
mergeWithExisting = true // Instead of copying, merge with existing metadata in the output directories.
}
/*
By default, if `-Pagent` is specified, all tasks that extend JavaForkOptions are instrumented.
This can be limited to only specific tasks that match this predicate.
*/
tasksToInstrumentPredicate = t -> true
}
binaries {
main {
// Main options
imageName = 'application' // The name of the native image, defaults to the project name
mainClass = 'org.test.Main' // The main class to use, defaults to the application.mainClass
debug = true // Determines if debug info should be generated, defaults to false (alternatively add --debug-native to the CLI)
verbose = true // Add verbose output, defaults to false
fallback = true // Sets the fallback mode of native-image, defaults to false
sharedLibrary = false // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included
quickBuild = false // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable, or add --native-quick-build to the CLI)
richOutput = false // Determines if native-image building should be done with rich output
requiredVersion = '22.3' // The minimal GraalVM version, can be `MAJOR`, `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`
systemProperties = [name1: 'value1', name2: 'value2'] // Sets the system properties to use for the native image builder
configurationFileDirectories.from(file('src/my-config')) // Adds a native image configuration file directory, containing files like reflection configuration
excludeConfig.put("org.example.test:artifact:version", ["^/META-INF/native-image/.*", "^/config/.*"]) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates.
excludeConfig.put(file("path/to/artifact.jar"), listOf("^/META-INF/native-image/.*", "^/config/.*"))
// Advanced options
buildArgs.add('--link-at-build-time') // Passes '--link-at-build-time' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension
jvmArgs.add('flag') // Passes 'flag' directly to the JVM running the native image builder
// Runtime options
runtimeArgs.add('--help') // Passes '--help' to built image, during "nativeRun" task
useFatJar = true // Instead of passing each jar individually, builds a fat jar
}
}
}
graalvmNative {
agent {
defaultMode.set("standard") // Default agent mode if one isn't specified using `-Pagent=mode_name`
enabled.set(true) // Enables the agent
modes {
// The standard agent mode generates metadata without conditions.
standard {
}
// The conditional agent mode generates metadata with conditions.
conditional {
userCodeFilterPath.set("path-to-filter.json") // Path to a filter file that determines classes which will be used in the metadata conditions.
extraFilterPath.set("path-to-another-filter.json") // Optional, extra filter used to further filter the collected metadata.
}
// The direct agent mode allows users to directly pass options to the agent.
direct {
// {output_dir} is a special string expanded by the plugin to where the agent files would usually be output.
options.add("config-output-dir={output_dir}")
options.add("experimental-configuration-with-origins")
}
}
callerFilterFiles.from("filter.json")
accessFilterFiles.from("filter.json")
builtinCallerFilter.set(true)
builtinHeuristicFilter.set(true)
enableExperimentalPredefinedClasses.set(false)
enableExperimentalUnsafeAllocationTracing.set(false)
trackReflectionMetadata.set(true)
// Copies metadata collected from tasks into the specified directories.
metadataCopy {
inputTaskNames.add("test") // Tasks previously executed with the agent attached.
outputDirectories.add("src/main/resources/META-INF/native-image")
mergeWithExisting.set(true) // Instead of copying, merge with existing metadata in the output directories.
}
/*
By default, if `-Pagent` is specified, all tasks that extend JavaForkOptions are instrumented.
This can be limited to only specific tasks that match this predicate.
*/
tasksToInstrumentPredicate.set(t -> true)
}
binaries {
named("main") {
// Main options
imageName.set("application") // The name of the native image, defaults to the project name
mainClass.set("org.test.Main") // The main class to use, defaults to the application.mainClass
debug.set(true) // Determines if debug info should be generated, defaults to false (alternatively add --debug-native to the CLI)
verbose.set(true) // Add verbose output, defaults to false
fallback.set(true) // Sets the fallback mode of native-image, defaults to false
sharedLibrary.set(false) // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included
quickBuild.set(false) // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable, or add --native-quick-build to the CLI)
richOutput.set(false) // Determines if native-image building should be done with rich output
requiredVersion.set('22.3') // The minimal GraalVM version, can be `MAJOR`, `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`
systemProperties.putAll(mapOf("name1" to "value1", "name2" to "value2")) // Sets the system properties to use for the native image builder
configurationFileDirectories.from(file("src/my-config")) // Adds a native image configuration file directory, containing files like reflection configuration
excludeConfig.put("org.example.test:artifact:version", listOf("^/META-INF/native-image/.*", "^/config/.*")) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates.
excludeConfig.put(file("path/to/artifact.jar"), listOf("^/META-INF/native-image/.*", "^/config/.*"))
// Advanced options
buildArgs.add("--link-at-build-time") // Passes '--link-at-build-time' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension
jvmArgs.add("flag") // Passes 'flag' directly to the JVM running the native image builder
// Runtime options
runtimeArgs.add("--help") // Passes '--help' to built image, during "nativeRun" task
useFatJar.set(true) // Instead of passing each jar individually, builds a fat jar
}
}
}
For options that can be set using command-line, if both DSL and command-line options are present, command-line options take precedence. |
Max parallel builds
When using Gradle parallel builds, the plugin will automatically limit the number of native images which can be built concurrently, in order to limit CPU and memory usage.
By default, it’s limited to the number of CPU cores / 16, but you can change this limit either by setting the org.graalvm.buildtools.max.parallel.builds
gradle property (e.g in your gradle.properties
file) or by setting the GRAALVM_BUILDTOOLS_MAX_PARALLEL_BUILDS
environment variable.
Long classpath, @argument file and fat jar support
Since release 0.9.10, the plugin will automatically pass arguments to the native-image
tool using an argument file, which should prevent all long classpath issues under Windows.
However, if you are using an older GraalVM release (older than 21.3) which doesn’t support argument files, you will need to rely on creating a "fat jar", which includes all entries from the classpath automatically, to workaround the problem:
graalvmNative {
useArgFile = false // required for older GraalVM releases
binaries {
main {
useFatJar = true
}
}
}
graalvmNative {
useFatJar.set(false) // required for older GraalVM releases
binaries {
named("main") {
useFatJar.set(true)
}
}
}
Alternatively, it is possible to use your own fat jar (for example created using the Shadow plugin) by setting the classpathJar
property directly on the task:
tasks.named("nativeCompile") {
classpathJar = myFatJar
}
tasks.named<BuildNativeImageTask>("nativeCompile") {
classpathJar.set(myFatJar.flatMap { it.archiveFile })
}
When the classpathJar
property is set, the classpath
property is ignored.
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.
The minimum supported version is JUnit 5.8.1 (JUnit Platform 1.8.1, JUnit Jupiter 5.8.1, JUnit Vintage 5.8.1) |
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.
Currently, this feature requires the execution of the tests in the classic "JVM" mode prior to the execution of tests in native mode. To execute the tests, execute:
./gradlew nativeTest
Configuring test image options
You can fine-tune the test binary using the test
binary configuration.
The following example prints additional data for troubleshooting and sets the minimal optimizations.
graalvmNative {
binaries {
test {
buildArgs.addAll('--verbose', '-O0')
}
}
}
graalvmNative {
binaries {
named("main") {
mainClass.set("org.test.Main")
}
named("test") {
buildArgs.addAll("--verbose", "-O0")
}
}
}
Disabling testing support
There are cases where you might want to disable native testing support:
-
You don’t actually want to run your tests in native mode.
-
Your library or application uses a testing framework that is not supported on the JUnit Platform.
-
You need to use the agent when running tests on the JVM but do not wish to run those same tests in native mode.
In this case, you can disable native testing support by configuring the graalvmNative
extension as follows:
graalvmNative {
testSupport = false
}
graalvmNative {
testSupport.set(false)
}
Configuring additional test suites
It’s common to have multiple test source sets in a Gradle build. Typically, you may have an integration test suite, or a functional test suite, in addition to the unit test suite. The plugin supports running those tests as native binaries too.
For example, imagine that you have a source set named integTest
and that its corresponding test task is named integTest
.
In this case you can register a new native test binary via the graalvmNative
extension:
graalvmNative {
registerTestBinary("integTest") {
usingSourceSet(sourceSets.integTest)
forTestTask(integTest)
}
}
graalvmNative {
registerTestBinary("integTest") {
usingSourceSet(sourceSets.getByName("integTest"))
forTestTask(tasks.named<Test>("integTest"))
}
}
The plugin will then automatically create the following tasks:
-
nativeIntegTestCompile
, to compile a native image using theintegTest
source set -
nativeIntegTest
, to execute the tests in native mode
The same mechanism can be used if you have multiple test tasks for a single test source set, which is often the case with manual test sharding.
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 Gradle plugin simplifies generation of the required metadata files by injecting the agent automatically for you (this includes, but is not limited to the reflection file).
Any task that extends JavaForkOptions
(like test
, run
etc) can be instrumented by passing -Pagent
to gradle when running said tasks.
The agent can run in multiple modes that dictate how the metadata is collected and merged.
Once the metadata is collected, it can be copied into the project using the metadataCopy
task.
./gradlew -Pagent run # Runs on JVM with native-image-agent.
./gradlew metadataCopy --task run --dir src/main/resources/META-INF/native-image # Copies the metadata collected by the agent into the project sources
./gradlew nativeCompile # Builds image using metadata acquired by the agent.
# For testing
./gradlew -Pagent nativeTest # Runs on JVM with the native-image agent, collects the metadata and uses it for testing on native-image.
The agent can run in multiple modes:
-
Standard - Collects metadata without conditions. This is recommended if you are building an executable.
-
Conditional - Collects metadata with conditions. This is recommended if you are creating conditional metadata for a library intended for further use.
-
Direct - For advanced users only. This mode allows directly controlling the command line passed to the agent.
The default mode is specified in the DSL but can be changed by passing the mode name to Gradle when using the agent: -Pagent=conditional
The generated configuration files will be found in the ${buildDir}/native/agent-output/${taskName}
directory, for example, build/native/agent-output/run
.
The plugin will also substitute {output_dir}
in the agent options to point to this directory during the agent run.
Configuring agent options
The native agent can be configured with additional options.
This can be done using the agent
configuration block.
Each agent option has a corresponding field in the DSL.
See Configuration options for the full list of available options.
GraalVM Reachability Metadata Support
Since release 0.9.11, the plugin adds experimental support for the GraalVM reachability metadata repository. This repository provides reachability metadata for libraries that do not support GraalVM Native Image.
This version of the plugin defaults to the using the metadata repository in version 0.3.6. There is nothing for you to configure if you are fine with this version. The repository is also published on Maven Central at the following coordinates: org.graalvm.buildtools:graalvm-reachability-metadata:graalvm-reachability-metadata with the repository classifier and zip extension, e.g. graalvm-reachability-metadata-0.10.0-repository.zip .
|
Configuring the metadata repository
Metadata repository support is enabled by default. Support can be disabled explicitly:
graalvmNative {
metadataRepository {
enabled = false
}
}
graalvmNative {
metadataRepository {
enabled.set(false)
}
}
A metadata repository consists of configuration files for GraalVM. The plugin will automatically download the configuration metadata from the official repository if you supply the version of the repository you want to use:
graalvmNative {
metadataRepository {
version = "0.1.0"
}
}
graalvmNative {
metadataRepository {
version.set("0.1.0")
}
}
Alternatively, it is possible to use a local repository, in which case you can specify the path to the repository:
graalvmNative {
metadataRepository {
uri(file("metadata-repository"))
}
}
graalvmNative {
metadataRepository {
uri(file("metadata-repository"))
}
}
For each library included in the native image, the plugin will automatically search for GraalVM image build configuration metadata in the repository. In some cases, you may need to exclude a particular module from the search. This can be done by adding it to the exclude list:
graalvmNative {
metadataRepository {
// Exclude this library from automatic metadata
// repository search
excludes.add("com.company:some-library")
}
}
graalvmNative {
metadataRepository {
// Exclude this library from automatic metadata
// repository search
excludes.add("com.company:some-library")
}
}
Last, it is possible for you to override the metadata version of a particular module. This may be interesting if there’s no specific metadata available for the particular version of the library that you use, but that you know that a version works:
graalvmNative {
metadataRepository {
// Force the version of the metadata for a particular library
moduleToConfigVersion.put("com.company:some-library", "3")
}
}
graalvmNative {
metadataRepository {
// Force the version of the metadata for a particular library
moduleToConfigVersion.put("com.company:some-library", "3")
}
}
Including metadata repository files
By default, reachability metadata will be used only when your native image is generated. In some situations, you may want a copy of the reachability metadata to use directly.
For example, copying the reachability metadata into your jar can be useful when some other process is responsible for converting your jar into a native image. You might be generating a shaded jar and using a Paketo buildpack to convert it to a native image.
To download a copy of the metadata into the build/native-reachability-metadata
directory you can the collectReachabilityMetadata
task.
Files will be downloaded into META-INF/native-image/<groupId>/<versionId>
subdirectories.
To include metadata repository inside your jar you can link to the task using the jar
DSL from
directive:
tasks.named("jar") {
from collectReachabilityMetadata
}
tasks.named("jar", Jar) {
from(collectReachabilityMetadata)
}
For more advanced configurations you can declare a org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata
task and set the appropriate properties.
Profile-guided optimizations
The plugin supports building images with Profile-Guided Optimizations.
It works in 3 phases:
-
the first one consists in generating a binary with instrumentation enabled
-
the second phase consists in running the binary in order to gather profiling information
-
the third phase consists in compiling the binary with the generated profile
In order to generate a binary with instrumentation enabled, you should run the nativeCompile
command with the --pgo-instrument
command line option:
./gradlew nativeCompile --pgo-instrument
This will generate a binary under build/native/nativeCompile
with the -instrumented
suffix.
You can run the binary to gather profiling data:
$ cd build/native/nativeCompile/
$ ./my-application-instrumented`
A default.iprof
file will be generated once the application is stopped.
Alternatively, you can have Gradle both generate and run the instrumented binary in a single command by running:
./gradlew nativeCompile --pgo-instrument nativeRun
In which case the profile will automatically be stored into build/native/nativeCompile
.
The last phase consists in copying the generated profile, so that it’s automatically used when building the native binary.
The conventional location for profiles is src/pgo-profiles/<name of the binary>
.
By default, we’re using the main
binary so the location will be src/pgo-profiles/main
.
Copy the default.iprof
file into that directory, then run:
./gradlew nativeCompile
The profile will automatically be used and the binary compiled with PGO.
It is possible to include more than one profile, in which case you should rename the .iprof
files in the src/pgo-profiles/main
directory.
Configurations defined by the plugin
For each binary (main
and test
), the plugin declares 2 configurations that users or plugin authors can use to tweak the native image compilation classpath:
-
nativeImageCompileOnly
(for themain
binary) andnativeImageTestCompileOnly
(for thetest
binary) can be used to declare dependencies which are only needed at image compilation. -
nativeImageClasspath
(for themain
binary) andnativeImageTestClasspath
(for thetest
binary) are the configurations which are resolved to determine the image classpaths.
The native image "compile only" configurations can typically be used to declare dependencies which are only required when building a native binary, and therefore shouldn’t leak to the classic "JVM" runtime.
For example, you could declare a source set which uses the GraalVM SDK to implement native features. This source set would contain code which is only relevant to native images building:
sourceSets {
graal
}
dependencies {
graalCompileOnly 'org.graalvm.nativeimage:svm:21.2.0'
graalCompileOnly 'org.graalvm.sdk:graal-sdk:21.2.0'
nativeImageCompileOnly sourceSets.graal.output.classesDirs
}
configurations {
nativeImageClasspath.extendsFrom(graalImplementation)
}
val graal by sourceSets.creating
dependencies {
"graalCompileOnly"("org.graalvm.nativeimage:svm:21.2.0")
"graalCompileOnly"("org.graalvm.sdk:graal-sdk:21.2.0")
nativeImageCompileOnly(graal.output.classesDirs)
}
configurations {
nativeImageClasspath.extendsFrom(getByName("graalImplementation"))
}
Javadocs
In addition, you can consult the Javadocs of the plugin.