badge

The Gradle plugin for GraalVM Native Image adds support for building and testing native images using the Gradle build tool.

Find the differences between the versions in the project changelog.

You can find sample applications in the source repository.

Adding the Plugin

Refer to the Getting Started Gradle Guide which provides step-by-step directions on adding the Gradle plugin to your project, building your first native image, and running it.

The plugin requires that you install a GraalVM JDK.

The plugin is enabled by adding its declaration to the build.gradle / build.gradle.kts file within the plugins block:

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

This plugin supplements and heavily relies on regular Java plugins such as application, java-library, java, and others. Not having them included in your project will most probably cause errors.

You can use development versions of the plugin by adding the 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()
    }
}

Using Gradle Toolchains

If the Gradle version you are using is not compatible with the GraalVM version, you can set up the GRAALVM_HOME environment variable pointing to your GraalVM installation, and the JAVA_HOME environment variable pointing to some JDK that is compatible with the Gradle on your system.

Enabling Toolchain Detection

Toolchain support has many pitfalls. Unless you have a single JDK installed on your machine, which is the GraalVM version that you want to use, we do not recommend enabling them yet. We are working with the Gradle team on improvements in the future.

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.

Plugin Configuration

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

Available Tasks

The main tasks that you may want to execute are:

  • nativeCompile triggers the generation of a native executable of your application

  • nativeRun executes the generated native executable

  • nativeTestCompile builds a native image with tests found in the test source set

  • nativeTest executes 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 Configuration

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 {
        // options for the main binary
    }
    binaries.test {
        // options for the test binary
    }
    binaries.all {
        // common options for both main and test binaries
    }
}

The following configuration options are available for building native images:

graalvmNative {
    binaries {
        main {
            imageName = 'application'
            mainClass = 'org.test.Main'
            debug = true
            verbose = true
            fallback = true
            sharedLibrary = false
            quickBuild = false
            richOutput = false

            systemProperties = [name1: 'value1', name2: 'value2']
            configurationFileDirectories.from(file('src/my-config'))
            excludeConfig.put("org.example.test:artifact:version", ["^/META-INF/native-image/.*", "^/config/.*"])
            excludeConfig.put(file("path/to/artifact.jar"), listOf("^/META-INF/native-image/.*", "^/config/.*"))

            // Advanced options
            buildArgs.add('--link-at-build-time')
            jvmArgs.add('flag')

            // Runtime options
            runtimeArgs.add('--help')

            useFatJar = true
        }
    }

    agent {
        defaultMode = "standard"
        enabled = true

        modes {
            standard {
            }
            conditional {
                userCodeFilterPath = "path-to-filter.json"
                extraFilterPath = "path-to-another-filter.json" // Optional
            }
            direct {
                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

        metadataCopy {
            inputTaskNames.add("test")
            outputDirectories.add("META-INF/native-image/<groupId>/<artifactId>/")
            mergeWithExisting = true
        }

        tasksToInstrumentPredicate = t -> true
    }
}
graalvmNative {
    binaries {
        named("main") {
            imageName.set("application")
            mainClass.set("org.test.Main")
            debug.set(true)
            verbose.set(true)
            fallback.set(true)
            sharedLibrary.set(false)
            richOutput.set(false)
            quickBuild.set(false)

            systemProperties.putAll(mapOf("name1" to "value1", "name2" to "value2"))
            configurationFileDirectories.from(file("src/my-config"))
            excludeConfig.put("org.example.test:artifact:version", listOf("^/META-INF/native-image/.*", "^/config/.*"))
            excludeConfig.put(file("path/to/artifact.jar"), listOf("^/META-INF/native-image/.*", "^/config/.*"))

            // Advanced options
            buildArgs.add("--link-at-build-time")
            jvmArgs.add("flag")

            // Runtime options
            runtimeArgs.add("--help")

            useFatJar.set(true)
        }
    }

    agent {
        defaultMode.set("standard")
        enabled.set(true)

        modes {
            standard {
            }
            conditional {
                userCodeFilterPath.set("path-to-filter.json")
                extraFilterPath.set("path-to-another-filter.json") // Optional
            }
            direct {
                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)

        metadataCopy {
            inputTaskNames.add("test")
            outputDirectories.add("/META-INF/native-image/<groupId>/<artifactId>/")
            mergeWithExisting.set(true)
        }

        tasksToInstrumentPredicate.set(t -> true)
    }
}

Native Image options

  • imageName - The name of the native executable, defaults to the project name

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

  • debug - Determines if debug info should be generated; defaults to false (alternatively, add --debug-native to the CLI)

  • verbose - Adds verbose output (false by default)

  • fallback - Sets the fallback mode of native-image (false by default)

  • sharedLibrary - Determines if the image is a shared library

  • quickBuild - Determines if the image is being built in quick build mode

  • richOutput - Determines whether native-image building should produce a rich output

  • systemProperties: Defines the system properties to use for the native-image tool

  • configurationFileDirectories: Adds a directory containing configuration files for native-image, such as reflection configuration -excludeConfig: Excludes configuration that matches any of the specified regex patterns from the JAR of a dependency with the given coordinates

  • jvmArgs: Passes the specified arguments directly to the JVM running the native-image builder

  • useFatJar: Builds a fat JAR instead of passing each JAR individually

You can also pass build-time and run-time arguments:

  • buildArgs.add('<buildArg>'): Configures the build by passing options directly to native-image. You can pass any Native Image build option listed here.

  • runtimeArgs.add('<runtimeArg>'): Specifies runtime arguments consumed by your application.

For options that can be set using the command line, if both DSL and command-line options are present, command-line options take precedence.

Native Image Tracing Agent

If your project requires reflection, classpath resources, dynamic proxies, or other features that need explicit configuration, it may be helpful to first run your application or tests using the Native Image Tracing 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 (such as test, run, and so on) can be instrumented by passing -Pagent to Gradle when running those tasks.

Enable the agent by adding the following block inside graalvmNative:

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

You can then run it:

./gradlew -Pagent run # Runs on HotSpot with the agent

The generated configuration files can be found in the ${buildDir}/native/agent-output/${taskName} directory, for example, build/native/agent-output/run. The plugin also substitutes {output_dir} in the agent options to point to this directory during the agent run.

The agent can run in multiple modes that dictate how the metadata is collected and merged.

  • defaultMode - Specifies the default agent mode if one isn’t specified using -Pagent=mode_name

  • enabled - Enables the agent

  • modes- Configures the following agent modes:

    • standard - Generates metadata without conditions

    • conditional - Generates metadata with conditions

      • userCodeFilterPath - Path to a filter file that specifies which classes to include in metadata conditions

      • extraFilterPath - (Optional) An additional filter file to further refine the collected metadata

    • direct - Allows users to directly pass options to the agent

      • {output_dir} - The output directory to store agent files

  • tasksToInstrumentPredicate - By default, when -Pagent is specified, all tasks extending JavaForkOptions are instrumented. This can be limited to only specific tasks that match this predicate.

agent {
    enabled = true
    defaultMode = "conditional"
    modes {
        conditional {
            userCodeFilterPath = "src/test/native-image/filters/user-code-filter.json"
        }
        direct {
            options.add("config-output-dir=src/test/resources/direct-mode-metadata")
            options.add("experimental-configuration-with-origins")
        }
    }
}
agent {
    enabled.set(true)
    defaultMode.set("conditional")
    modes {
        conditional {
            userCodeFilterPath.set("src/test/native-image/filters/user-code-filter.json")
        }
        direct {
            options.add("config-output-dir=src/test/resources/direct-mode-metadata")
            options.add("experimental-configuration-with-origins")
        }
    }
}

If you want to enable the agent on the command line, you can specify in which mode you want to run it. For example:

./gradlew -Pagent=standard nativeTest
./gradlew -Pagent=conditional nativeTest
./gradlew -Pagent=direct nativeTest

Resources Autodetecting

You can instruct the plugin to automatically detect resources to be included in a native executable at build time: Add this to your build.gradle file:

graalvmNative {
    binaries.all {
        resources.autodetect()
    }
}

MetadataCopy Task

Once the metadata is collected, it can be copied into the project using the metadataCopy task. To do so, add the following block inside your agent block:

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

Inside this block, you configure:

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

Then you can copy metadata to the location you specified with:

./gradlew metadataCopy

You can configure the metadataCopy task on the command line as well:

./gradlew metadataCopy --task run  # if you used nativeRun (or just run) to collect metadata
./gradlew metadataCopy --task test  # if you used nativeTest (or just test) to collect metadata
./gradlew metadataCopy --dir <pathToSomeDirectory> # to specify the output directory

Common Agent Options

All the mentioned modes share certain common configuration options like:

  • callerFilterFiles

  • accessFilterFiles

  • builtinCallerFilter

  • builtinHeuristicFilter

  • enableExperimentalPredefinedClasses

  • enableExperimentalUnsafeAllocationTracing

  • trackReflectionMetadata

These options are for advanced usages. You can read more about them here.

A complete example of the agent block should look like this:

agent {
    defaultMode = "standard"
    enabled = true

    modes {
        conditional {
            userCodeFilterPath = "path-to-filter.json"
            extraFilterPath = "path-to-another-filter.json"
        }
        direct {
            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

    metadataCopy {
        inputTaskNames.add("test")
        outputDirectories.add("src/main/resources/META-INF/native-image/<groupId>/<artifactId>/")
        mergeWithExisting = true
    }
}
agent {
    defaultMode.set("standard")
    enabled.set(true)

    modes {
        conditional {
            userCodeFilterPath.set("path-to-filter.json")
            extraFilterPath.set("path-to-another-filter.json")
        }
        direct {
            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)

    metadataCopy {
        inputTaskNames.add("test")
        outputDirectories.add("src/main/resources/META-INF/native-image/<groupId>/<artifactId>/")
        mergeWithExisting.set(true)
    }
}

Reduce the Amount of Generated Metadata

In some cases the agent may include more metadata than it is actually needed. You can filter metadata using the agent filter files. These filter files that agent consumes have the following structure:

{
 "rules": [
    {"includeClasses": "some.class.to.include.**"},
    {"excludeClasses": "some.class.to.exclude.**"},
  ],
  "regexRules": [
    {"includeClasses": "regex\.example\.class.*"},
    {"excludeClasses": "regex\.example\.exclude[0-9]+"},
  ]
}

The process how you can pass the config files to the agent is described in the previous section.

You can see on some simple Java application how different filter files affect generated metadata.

Let’s start with this filter:

{
  "rules": [
    {"includeClasses": "**"}
  ]
}

This filter instructs the agent to include everything, which will result in a massive configuration file. For example, this is how reachability-metadata.json looks like:

{
  {
    "reflection": [
      {
        "condition": {
          "typeReached": "java.io.ObjectInputStream"
        },
        "type": "[Ljava.lang.Object;"
      },
      {
        "condition": {
          "typeReached": "java.io.ObjectInputStream"
        },
        "type": "java.util.LinkedHashSet"
      },
      {
        "condition": {
          "typeReached": "org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor"
        },
        "type": "org.example.NativeTests"
      },
      {
        "condition": {
          "typeReached": "org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor"
        },
        "type": "org.example.NativeTests",
        "allDeclaredFields": true
      },
      {
        "condition": {
          "typeReached": "org.junit.jupiter.engine.descriptor.ExtensionUtils"
        },
        "type": "org.example.NativeTests"
      },
      ...
    ],
    "resources": [
      {
        "condition": {
          "typeReached": "org.junit.platform.launcher.core.LauncherFactory"
        },
        "glob": "META-INF/services/org.junit.platform.engine.TestEngine"
      },
      {
        "condition": {
          "typeReached": "java.lang.ClassLoader"
        },
        "glob": "TestResource.txt"
      },
      ...
    ],
    "bundles": [],
    "jni": [
      {
        "condition": {
          "typeReached": "java.net.InetAddress"
        },
        "type": "java.lang.Boolean",
        "methods": [
          {
            "name": "getBoolean",
            "parameterTypes": [
              "java.lang.String"
            ]
          }
        ]
      }
    ]
  }

As you can see, there are lots of entries that you likely do not want. They are present because the metadata was generated using tests, and that the testing library was present at run time during the agent execution pass, but in practice, your application will not use the testing library in production. To reduce the amount of generated metadata, use the following user-code-filter.json:

{
  "rules": [
    {"includeClasses": "**"},
    {"excludeClasses": "org.junit.**"},
    {"excludeClasses": "org.gradle.**"},
    {"excludeClasses": "worker.org.gradle.**"},
    {"excludeClasses": "org.slf4j.**"},
    {"excludeClasses": "java.**"}
  ]
}

Always be careful when removing entries in metadata, as this may result in a broken native image.

After updating the filter, you can regenerate the metadata, which will result in the following reachability-metadata.json file:

{
  "reflection": [
    {
      "condition": {
        "typeReached": "org.example.NativeTests"
      },
      "type": "org.example.NativeTests$Person",
      "allDeclaredFields": true
    },
    {
      "condition": {
        "typeReached": "sun.security.jca.GetInstance"
      },
      "type": "sun.security.provider.SHA",
      "methods": [
        {
          "name": "<init>",
          "parameterTypes": []
        }
      ]
    }
  ],
  "resources": [
    {
      "condition": {
        "typeReached": "org.example.NativeTests"
      },
      "glob": "TestResource.txt"
    }
  ],
  "bundles": []
}

As you can see there are no more entries that contain classes from org.junit (as their condition).

Max Parallel Builds

When using Gradle parallel builds, the plugin automatically limits the number of native images which can be built concurrently, in order to limit CPU and memory usage. By default, it is 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 (in your gradle.properties file), or by setting the GRAALVM_BUILDTOOLS_MAX_PARALLEL_BUILDS environment variable.

Long classpath, @argument File, and a Fat JAR Support

The plugin automatically passes arguments to the native-image tool from the 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 JAR file (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

The plugin supports running tests on the JUnit Platform as native images. This means that tests are 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 the plugin as long as the programming language used by the TestEngine and the programming language used to write the tests is supported by GraalVM Native Image. The 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 on the JVM prior to the execution of tests as native code. To execute the tests, run:

./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 running native tests:

  • You don’t actually want to run your tests as native code.

  • 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 the same tests as native code.

In this case, you can disable native testing support by configuring the graalvmNative option as follows:

graalvmNative {
    testSupport = false
}
graalvmNative {
    testSupport.set(false)
}

Configuring Additional Test Suites

It is 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 with graalvmNative:

graalvmNative {
    registerTestBinary("integTest") {
        usingSourceSet(sourceSets.integTest)
        forTestTask(integTest)
    }
}
graalvmNative {
    registerTestBinary("integTest") {
        usingSourceSet(sourceSets.getByName("integTest"))
        forTestTask(tasks.named<Test>("integTest"))
    }
}

The plugin then automatically creates the following tasks:

  • nativeIntegTestCompile to compile a native image using the integTest source set

  • nativeIntegTest to execute the tests as native code

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.

GraalVM Reachability Metadata Support

The plugin adds support for the GraalVM Reachability Metadata Repository. This repository provides the configuration for libraries that do not support GraalVM Native Image.

The GraalVM Reachability Metadata 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, for example, graalvm-reachability-metadata-0.10.5-repository.zip.

Configuring the Metadata Repository

The plugin automatically downloads the configuration metadata from the official repository if you supply the version of the repository you want to use.

The support is enabled by default, but can be disabled explicitly:

graalvmNative {
    metadataRepository {
        enabled = false
    }
}
graalvmNative {
    metadataRepository {
        enabled.set(false)
    }
}

To override the repository version, 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 dependency, the plugin automatically searches for the 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")
    }
}

Lastly, it is possible for you to override the metadata version of a particular module. This may be interesting if there is 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 is used only when your native image is being generated. In some situations, you may want a copy of the reachability metadata to use it directly.

Copying the reachability metadata into the application JAR can be useful when some other process is responsible for converting your JAR into a native image. For example, you might be generating a shaded JAR file 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 the JAR file, 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 the org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata task and set the appropriate properties.

Profile-Guided Optimization

The plugin supports building optimized images with Profile-Guided Optimization (PGO) to improve performance and throughput.

PGO is available in Oracle GraalVM.

The PGO workflow includes three steps:

  • First, generate a native image with instrumentation enabled.

  • Next, run the image to gather profiling information.

  • Then, create an optimized native image based on the profiles.

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 generates a native image under build/native/nativeCompile with the -instrumented suffix. You can run this image to gather profiling data:

./myApp-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 this case, the profile will automatically be stored under build/native/nativeCompile.

The last phase consists in copying the generated profile, so that it is automatically used when building an optimized native image. The conventional location for profiles is src/pgo-profiles/<name of the binary>. By default, the location will be src/pgo-profiles/main. Copy default.iprof into that directory, and then run:

./gradlew nativeCompile

The profile will automatically be used. If everything was done properly, you will see "PGO: user-provided" in the native image build output.

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. Learn more about PGO on the website.

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 during the ahead-of-time compilation.

  • nativeImageClasspath (for the main binary) and nativeImageTestClasspath (for the test binary) are the configurations which are resolved to determine the image classpath.

The native image "compile only" configurations can typically be used to declare dependencies which are only required when building a native binary, and therefore should not leak when running on the JVM.

For example, you can 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

For further reference, you can review the Javadocs of the plugin.