Toolchains for JVM projects
By default, Gradle uses the same Java version for running Gradle itself and building JVM projects.
This is not always desirable. Building projects with different Java versions on different developer machines and CI servers may lead to unexpected issues. Additionally, you may want to build a project using a Java version that is not supported for running Gradle.
A Java Toolchain (from now on referred to simply as toolchain) is a set of tools, usually taken from a local JRE/JDK installation that are used to configure different aspects of a build.
Compile tasks may use javac
as their compiler, test and exec tasks may use the java
command while javadoc
will be used to generate documentation.
Consuming Toolchains
A build can globally define what toolchain it targets by stating the Java Language version it needs and optionally the vendor:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
Executing the build (e.g. using gradle check
) will now handle several things for you and others running your build:
-
Setup all compile, test and javadoc tasks to use the defined toolchain which may be different than the one Gradle itself uses
-
Gradle detects locally installed JVMs
-
Gradle chooses a JRE/JDK matching the requirements of the build (in this case a JVM supporting Java 11)
-
If no matching JVM is found, it will automatically download a matching JDK from AdoptOpenJDK
Toolchain support is available in the Java plugins and for the tasks they define. For the Groovy plugin, compilation is supported but not yet Groovydoc generation. For the Scala plugin, compilation and Scaladoc generation are supported. |
Using toolchains by specific vendors
In case your build has specific requirements from the used JRE/JDK, you may want to define the vendor for the toolchain as well.
JvmVendorSpec
has a list of well-known JVM vendors recognized by Gradle.
The advantage is that Gradle can handle any inconsistencies across JDK versions in how exactly the JVM encodes the vendor information.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.ADOPTIUM
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
vendor.set(JvmVendorSpec.ADOPTIUM)
}
}
If the vendor you want to target is not a known vendor, you can still restrict the toolchain to those matching the java.vendor
system property of the available toolchains.
Given the snippet below, only toolchain are taken into accounts whose java.vendor
property contains the given match string.
The matching is done in a case-insensitive manner.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.matching("customString")
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
vendor.set(JvmVendorSpec.matching("customString"))
}
}
Selecting toolchains by their virtual machine implementation
If your project requires a specific implementation, you can filter based on the implementation as well. Currently available implementations to choose from are:
VENDOR_SPECIFIC
-
Acts as a placeholder and matches any implementation from any vendor (e.g. hotspot, zulu, …)
J9
-
Matches only virtual machine implementations using the OpenJ9/IBM J9 runtime engine.
For example, to use an IBM Semeru JVM, distributed via AdoptOpenJDK, you can specify the filter as shown in the example below.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.IBM_SEMERU
implementation = JvmImplementation.J9
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
vendor.set(JvmVendorSpec.IBM_SEMERU)
implementation.set(JvmImplementation.J9)
}
}
The Java major version, the vendor (if specified) and implementation (if specified) will be tracked as an input for compilation and test execution. |
Configuring toolchain specifications
Gradle allows configuring multiple properties that affect the selection of a toolchain, such as language version or vendor. Even though these properties can be configured independently, the configuration must follow certain rules in order to form a valid specification.
A JavaToolchainSpec
is considered valid in two cases:
-
when no properties have been set, i.e. the specification is empty;
-
when
languageVersion
has been set, optionally followed by setting any other property.
In other words, if a vendor or an implementation are specified, they must be accompanied by the language version. Gradle distinguishes between toolchain specifications that configure the language version and the ones that do not. A specification without a language version, in most cases, would be treated as a one that selects the toolchain of the current build.
Usage of invalid instances of JavaToolchainSpec
results in a build error since Gradle 8.0.
Specify custom toolchains for individual tasks
In case you want to tweak which toolchain is used for a specific task, you can specify the exact tool a task is using.
For example, the Test
task exposes a JavaLauncher
property that defines which java executable to use for launching the tests.
In the example below, we configure all java compilation tasks to use JDK8.
Additionally, we introduce a new Test
task that is going to run our unit tests but using a JDK 14.
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(8)
}
}
task('testsOn14', type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(14)
}
}
tasks.withType<JavaCompile>().configureEach {
javaCompiler.set(javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(8))
})
}
tasks.register<Test>("testsOn14") {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(14))
})
}
In addition, in the application
subproject, we add another Java execution task to run our application with JDK 14.
task('runOn14', type: JavaExec) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(14)
}
classpath = sourceSets.main.runtimeClasspath
mainClass = application.mainClass
}
tasks.register<JavaExec>("runOn14") {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(14))
})
classpath = sourceSets["main"].runtimeClasspath
mainClass.set(application.mainClass)
}
Depending on the task, a JRE might be enough while for other tasks (e.g. compilation), a JDK is required. By default, Gradle prefers installed JDKs over JREs if they can satisfy the requirements.
Toolchains tool providers can be obtained from the javaToolchains
extension.
Three tools are available:
-
A
JavaCompiler
which is the tool used by the JavaCompile task -
A
JavaLauncher
which is the tool used by the JavaExec or Test tasks -
A
JavadocTool
which is the tool used by the Javadoc task
Integration with tasks relying on a Java executable or Java home
Any tasks that can be configured with a path to a Java executable, or a Java home location, can benefit from toolchains.
While you will not be able to wire a toolchain tool directly, they all have metadata that gives access to their full path or to the path of the Java installation they belong to.
For example, you can configure the java
executable for a task as follows:
def launcher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.named('sampleTask') {
javaExecutable = launcher.map { it.executablePath }
}
val launcher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.sampleTask {
javaExecutable.set(launcher.map { it.executablePath })
}
Another example, you can configure the Java Home for a task as follows:
def launcher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.named('anotherSampleTask') {
javaHome = launcher.map { it.metadata.installationPath }
}
val launcher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.anotherSampleTask {
javaHome.set(launcher.map { it.metadata.installationPath })
}
Yet another example, you can configure the Java compiler executable for a task as follows:
def compiler = javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.named('yetAnotherSampleTask') {
javaCompilerExecutable = compiler.map { it.executablePath }
}
val compiler = javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(11))
}
tasks.yetAnotherSampleTask {
javaCompilerExecutable.set(compiler.map { it.executablePath })
}
The examples above use tasks with Doing respectively |
Auto detection of installed toolchains
By default, Gradle automatically detects local JRE/JDK installations so no further configuration is required by the user. The following is a list of common package managers, tools, and locations that are supported by the JVM auto-detection.
JVM auto-detection knows how to work with:
-
Operation-system specific locations: Linux, macOS, Windows
-
Maven Toolchain specifications
-
IntelliJ IDEA installations
Among the set of all detected JRE/JDK installations, one will be picked according to the Toolchain Precedence Rules.
How to disable auto-detection
In order to disable auto-detection, you can use the org.gradle.java.installations.auto-detect
Gradle property:
-
Either start gradle using
-Porg.gradle.java.installations.auto-detect=false
-
Or put
org.gradle.java.installations.auto-detect=false
into yourgradle.properties
file.
Auto-Provisioning
If Gradle can’t find a locally available toolchain, which matches the requirements of the build, it can automatically download one. By default, downloading happens from Adoptium or AdoptOpenJDK, but this behaviour will be removed in future versions and a generic mechanism for using plugin-provided toolchain repositories will take over. For further details see the Toolchain Download Repositories section below.
By default, downloading will request a HotSpot JDK matching the current operating system and architecture. Provisioned JDKs are installed in the Gradle User Home directory.
Gradle will only download JDK versions for GA releases. There is no support for downloading early access versions. |
Once a provisioned JDK has been installed in the Gradle User Home directory it becomes one of the JDKs visible to auto-detection and can be used by any subsequent builds, just like any other JDK installed on the system. Since auto-provisioning only kicks in when auto-detection fails to find a matching JDK, auto-provisioning can only download new JDKs and is in no way involved in updating any of the already installed ones. None of the auto-provisioned JDKs will ever be revisited and automatically updated by auto-provisioning, even if there is a newer minor version available for them.
Adoptium and AdoptOpenJDK
Up until Gradle 8 (not included), the public Adoptium and AdoptOpenJDK APIs are used by default to determine and download a matching JDK. Gradle will try Adoptium first, and if it fails to find a matching JDK, it will try AdoptOpenJDK.
Due to changes in AdoptOpenJDK and the migration to Eclipse Adoptium, the endpoint now serves JDKs from Eclipse Adoptium or IBM Semeru and no longer builds from AdoptOpenJDK.
Using |
In case you want to use another server that is compatible with v3 of the AdoptOpenJDK API, you can point Gradle to use a different host. For that you use the Gradle property as in the example below:
org.gradle.jvm.toolchain.install.adoptopenjdk.baseUri=https://api.company.net/
You can also override the Adoptium API host with org.gradle.jvm.toolchain.install.adoptium.baseUri
.
Only secure protocols like |
Toolchain Download Repositories
Starting with Gradle 7.6, there is a generic way to configure arbitrary repositories to download toolchains from.
This new mechanism has priority over the Adoptium/AdoptOpenJDK built-in default, if configured the default will no longer be used for downloading. In future versions, the built-in default will be completely removed. |
Configuration toolchain download repositories starts by applying one or more settings plugins, which provide implementations for them. For details on writing plugins for toolchain provisioning, consult the Toolchain Resolver Plugins page.
Let’s say there is a plugin replicating the deprecated AdoptiumJDK download behavior and when applied, would register a repository implementation class named AdoptResolver
. Let’s say there is also a second plugin for downloading Azul JDKs, which, when applied registers the AzulResolver
implementation.
To actually make use of these two repositories in your build, you would use the new toolchainManagement
block available in your settings file, like this:
toolchainManagement {
jvm { (1)
javaRepositories {
repository('azul') { (2)
resolverClass = AzulResolver
credentials {
username "user"
password "password"
}
authentication {
digest(BasicAuthentication)
} (3)
}
repository('adoptium') { (4)
resolverClass = AdoptiumResolver
}
}
}
}
toolchainManagement {
jvm { (1)
javaRepositories {
repository("azul") { (2)
resolverClass.set(AzulResolver::class.java)
credentials {
username = "user"
password = "password"
}
authentication {
create<DigestAuthentication>("digest")
} (3)
}
repository("adoptium") { (4)
resolverClass.set(AdoptiumResolver::class.java)
}
}
}
}
1 | toolchainManagement is intended to cover more than Java toolchains, so the jvm sub-block provides the container for Java toolchain management related configuration |
2 | the javaRepositories container can be used to define named Java toolchain repository configurations; the configurations are linked to the actual repository implementations via the resolverClass property |
3 | toolchain download repositories primarily provide URIs to download toolchains from, but they can also be configured with the same set of authentication and authorization options that are available for artifact repositories used in dependency management |
4 | the repositories form an ordered list; when a specific toolchain needs to be downloaded, the list of repositories is examined in order and the first repository, that can provide the target toolchain, will be used |
The Although the authentication & authorization mechanisms are shared between the two, toolchain download repositories have nothing else in common with the artifact repositories used by Gradle in the context of dependency management. They represent conceptually different things. |
Viewing and debugging toolchains
Gradle can display the list of all detected toolchains including their metadata.
For example, to show all toolchains of a project, run:
gradle -q javaToolchains
gradle -q javaToolchains
> gradle -q javaToolchains + Options | Auto-detection: Enabled | Auto-download: Enabled + AdoptOpenJDK 1.8.0_242 | Location: /Users/username/myJavaInstalls/8.0.242.hs-adpt/jre | Language Version: 8 | Vendor: AdoptOpenJDK | Architecture: x86_64 | Is JDK: false | Detected by: system property 'org.gradle.java.installations.paths' + Microsoft JDK 16.0.2+7 | Location: /Users/username/.sdkman/candidates/java/16.0.2.7.1-ms | Language Version: 16 | Vendor: Microsoft | Architecture: aarch64 | Is JDK: true | Detected by: SDKMAN! + OpenJDK 15-ea | Location: /Users/user/customJdks/15.ea.21-open | Language Version: 15 | Vendor: AdoptOpenJDK | Architecture: x86_64 | Is JDK: true | Detected by: environment variable 'JDK16' + Oracle JDK 1.7.0_80 | Location: /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre | Language Version: 7 | Vendor: Oracle | Architecture: x86_64 | Is JDK: false | Detected by: macOS java_home
This can help to debug which toolchains are available to the build, how they are detected and what kind of metadata Gradle knows about those toolchains.
How to disable auto provisioning
In order to disable auto-provisioning, you can use the org.gradle.java.installations.auto-download
Gradle property:
-
Either start gradle using
-Porg.gradle.java.installations.auto-download=false
-
Or put
org.gradle.java.installations.auto-download=false
into agradle.properties
file.
Custom Toolchain locations
If auto-detecting local toolchains is not sufficient or disabled, there are additional ways you can let Gradle know about installed toolchains.
If your setup already provides environment variables pointing to installed JVMs, you can also let Gradle know about which environment variables to take into account.
Assuming the environment variables JDK8
and JRE14
point to valid java installations, the following instructs Gradle to resolve those environment variables and consider those installations when looking for a matching toolchain.
org.gradle.java.installations.fromEnv=JDK8,JRE14
Additionally, you can provide a comma-separated list of paths to specific installations using the org.gradle.java.installations.paths
property.
For example, using the following in your gradle.properties
will let Gradle know which directories to look at when detecting JVMs.
Gradle will treat these directories as possible installations but will not descend into any nested directories.
org.gradle.java.installations.paths=/custom/path/jdk1.8,/shared/jre11
Custom toolchains are not prioritized over auto-detected ones. If auto-detection is turned on, custom toolchains can still be specified but all they will do is to extend the set of detected JRE/JDK locations. Among these locations, one will be picked according to the Toolchain Precedence Rules. |
Precedence
Gradle will sort all the JDK/JRE installations matching the toolchain specification of the build and will pick the first one. Sorting is done based on the following rules:
-
the installation currently running Gradle is preferred over any other
-
JDK installations are preferred over JRE ones
-
certain vendors take precedence over others; their ordering (from the highest priority to lowest):
-
ADOPTIUM
-
ADOPTOPENJDK
-
AMAZON
-
APPLE
-
AZUL
-
BELLSOFT
-
GRAAL_VM
-
HEWLETT_PACKARD
-
IBM
-
IBM_SEMERU
-
MICROSOFT
-
ORACLE
-
SAP
-
everything else
-
-
higher major versions take precedence over lower ones
-
higher minor versions take precedence over lower ones
-
installation paths take precedence according to their lexicographic ordering (last resort criteria for deterministically deciding between installations of the same type, from the same vendor and with the same version)
All these rules are applied as multilevel sorting criteria, in the order they are shown. Let’s illustrate with an example. Let’s say that the only criteria we have added to our toolchain specification is that we want Java version 17 and that on our local machine following matching installations have been detected: Oracle JRE v17.0.0, Oracle JDK v17.0.1, Microsoft JDK 17.0.0, Microsoft JRE 17.0.1, Microsoft JDK 17.0.1. Let’s also assume that Gradle is being run by an installation with a different major version, so it’s not a match of the toolchain specification (the point being that otherwise that installation would have priority over every other one and our example would be less informative).
When we apply the above rules to sort this set we will end up with following ordering:
-
Microsoft JDK 17.0.1
-
Microsoft JDK 17.0.0
-
Oracle JDK v17.0.0
-
Microsoft JRE v17.0.1
-
Oracle JRE v17.0.1
The JREs end up last after applying the rule of preferring JDKs over JREs. Then in both categories the Microsoft installations end up first after applying the rule of preferring that vendor over Oracle. Then the two Microsoft JDKs get ordered further by the rule saying that we prefer higher version numbers over lower ones. Applying any more rules won’t change the final order.
The installation that will be picked is the first one, the Microsoft JDK 17.0.1.
Toolchains for plugin authors
Custom tasks that require a tool from the JDK should expose a Property<T>
with the desired tool as generic type.
The property should be declared as a @Nested
input.
By injecting the JavaToolchainService
in the plugin or task, it is also possible to wire a convention in those properties by obtaining the JavaToolchainSpec
from the java
extension on the project.
The example below showcases how to use the default toolchain as convention while allowing users to individually configure the toolchain per task.
import javax.inject.Inject;
abstract class CustomTaskUsingToolchains extends DefaultTask {
@Nested
abstract Property<JavaLauncher> getLauncher()
@Inject
CustomTaskUsingToolchains() {
// Access the default toolchain
def toolchain = project.getExtensions().getByType(JavaPluginExtension.class).toolchain
// acquire a provider that returns the launcher for the toolchain
JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class)
Provider<JavaLauncher> defaultLauncher = service.launcherFor(toolchain);
// use it as our default for the property
launcher.convention(defaultLauncher);
}
@TaskAction
def showConfiguredToolchain() {
println launcher.get().executablePath
println launcher.get().metadata.installationPath
}
}
plugins {
id 'java'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
tasks.register('showDefaultToolchain', CustomTaskUsingToolchains)
tasks.register('showCustomToolchain', CustomTaskUsingToolchains) {
launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
}
import javax.inject.Inject;
abstract class CustomTaskUsingToolchains : DefaultTask {
@get:Nested
abstract val launcher: Property<JavaLauncher>
@Inject
constructor() {
// Access the default toolchain
val toolchain = project.extensions.getByType<JavaPluginExtension>().toolchain
// acquire a provider that returns the launcher for the toolchain
val service = project.extensions.getByType<JavaToolchainService>()
val defaultLauncher = service.launcherFor(toolchain)
// use it as our default for the property
launcher.convention(defaultLauncher);
}
@TaskAction
fun showConfiguredToolchain() {
println(launcher.get().executablePath)
println(launcher.get().metadata.installationPath)
}
}
plugins {
java
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}
tasks.register<CustomTaskUsingToolchains>("showDefaultToolchain")
tasks.register<CustomTaskUsingToolchains>("showCustomToolchain") {
launcher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
})
}
With the property correctly configured as |