badge

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

Adding the plugin

Add following to plugins section of your project’s build.gradle / build.gradle.kts:

Applying the plugin
plugins {
  // ...

  // Apply GraalVM Native Image plugin
  id 'org.graalvm.buildtools.native' version '0.9.13'
}
plugins {
  // ...

  // Apply GraalVM Native Image plugin
  id("org.graalvm.buildtools.native") version "0.9.13"
}

The plugin isn’t available on the Gradle Plugin Portal yet, so you will need to declare a plugin repository in addition:

Add the following to your settings.gradle / settings.gradle.kts:

Declaring the plugin repository
pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
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.

pluginManagement {
    repositories {
        maven {
            url "https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots"
        }
        gradlePluginPortal()
    }
}
pluginManagement {
    repositories {
        maven {
            url = uri("https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots")
        }
        gradlePluginPortal()
    }
}

Installing GraalVM Native Image tool

The plugin relies on Gradle’s JVM toolchain support, allowing to decorrelate the tool used to run Gradle, the compiler used to build your application, and eventually the SDK used to generate a native image.

In practice, it means that this plugin will try to locate a suitable installation of GraalVM for you, even if you don’t run Gradle itself with GraalVM. For this, it will look into conventional places on your machine, including from installations done by popular tools like SDKMAN! or Jabba.

Even if you have a GraalVM SDK installed, Gradle will not automatically detect if native-image is also installed. Therefore, you will need to make sure that you have executed gu install native-image as indicated in the setup instructions.

If Gradle cannot find a GraalVM installation on the machine, it will fail with an error like this:

> No compatible toolchains found for request filter: {languageVersion=11, vendor=matching('GraalVM'), implementation=vendor-specific} (auto-detect true, auto-download true)

This happens because there’s no automatic provisioning of the GraalVM toolchain available yet, so you will have to install it first. Follow the following instructions to install it properly.

Alternatively, you may choose to:

  1. Disable toolchain support

  2. Run Gradle itself with a GraalVM SDK

  3. Set up a GRAALVM_HOME environment variable pointing to your GraalVM installation

Note that none of the above options is recommended as they are more fragile.

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 the test source set

  • nativeTest, which will execute tests found in the test 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.

Selecting the GraalVM toolchain

By default, the plugin will select a Java 11 GraalVM toolchain. If you want to use a different toolchain, for example a GraalVM Community Edition for Java 8, you can configure the toolchain like this:

Selecting the GraalVM toolchain
graalvmNative {
    binaries {
        main {
            javaLauncher = javaToolchains.launcherFor {
                languageVersion = JavaLanguageVersion.of(8)
                vendor = JvmVendorSpec.matching("GraalVM Community")
            }
        }
    }
}
graalvmNative {
    binaries {
        named("main") {
            javaLauncher.set(javaToolchains.launcherFor {
                languageVersion.set(JavaLanguageVersion.of(8))
                vendor.set(JvmVendorSpec.matching("GraalVM Community"))
            })
        }
    }
}
Disabling toolchain detection

Because of limitations in Gradle, the plugin may not be able to properly detect the toolchain. This is the case if, for example, you want to use GraalVM Enterprise or you want to be able to select a particular version of GraalVM.

To work around this problem, you can disable toolchain detection:

Disabling toolchain detection
graalvmNative {
    toolchainDetection = false
}
graalvmNative {
    toolchainDetection.set(false)
}

If you do this, the plugin will search for 2 environment variables: GRAALVM_HOME and JAVA_HOME in that order. If one of them is set, it will assume that it points to a valid GraalVM installation and completely bypass toolchain selection. Therefore, it becomes your responsibility to make sure that the environment variable points to a JDK that is compatible with your build script requirements (in particular, the language version).

Configuration options

The following configuration options are available for building images:

NativeImageOption configuration
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
            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)
            richOutput = false // Determines if native-image building should be done with rich output

            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", ["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('-H:Extra') // Passes '-H:Extra' 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
            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)
            richOutput.set(false) // Determines if native-image building should be done with rich output

            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", 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("-H:Extra") // Passes '-H:Extra' 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.

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:

Enabling the fat jar creation
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:

Enabling a custom fat jar creation
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.

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
This plugin requires JUnit Platform 1.8 or higher.

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:

Disabling testing support
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:

Registering a new test suite
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 the integTest 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.

Enabling the metadata repository

Support needs to be enabled explicitly:

Enabling the metadata repository
graalvmNative {
    metadataRepository {
        enabled = true
    }
}
graalvmNative {
    metadataRepository {
        enabled.set(true)
    }
}

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:

Enabling the metadata repository
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:

Using a local repository
graalvmNative {
    metadataRepository {
        uri(file("metadata-repository"))
    }
}
graalvmNative {
    metadataRepository {
        uri(file("metadata-repository"))
    }
}

Configuring the metadata repository

Once activated, for each library included in the native image, the plugin will automatically search for GraalVM reachability 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:

Excluding a module from search
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:

Specifying the metadata version to use for a particular library
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")
    }
}

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 the main binary) and nativeImageTestCompileOnly (for the test binary) can be used to declare dependencies which are only needed at image compilation.

  • nativeImageClasspath (for the main binary) and nativeImageTestClasspath (for the test 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:

Declaring a custom source set
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.