Introducing Java toolchains

Table of Contents

Introduction

Building a Java project and running Gradle both require the installation of a JDK. Most Gradle users conveniently use the Java installation that is running Gradle to build and test their application.

While this is acceptable in simple cases, there are a number of issues with this approach. For example, if a build requires the same Java version to be used on all machines, each developer needs to know about that requirement and install that version manually.

It has been possible to configure Gradle to build a project with a different Java version than the one used to run Gradle. However, it has required configuring each task like compilation, test, and javadoc separately.

Gradle 6.7 introduces “Java toolchain support”. In a nutshell, it allows you to run Gradle with whatever version of Java you have installed, while Gradle builds the project with the version of Java declared in a single place. Gradle will automatically configure the build, detect already installed JDKs and download the required JDK if it is not already installed.

No more release or sourceCompatibility tweaks, no more wiki pages describing which JDK you should install for the build to work. The advantages are tremendous, let’s see how to use this!

Introducing Java toolchains support #

Java toolchains support enables build authors to declare which Java version their project requires for compiling, testing and running their code.

Project wide configuration #

Let’s start with what you, as a build author, need to do, at the minimum:

plugins {
    id("java-library") // or id("application")
}

java {
    toolchain {
            languageVersion.set(JavaLanguageVersion.of(11))
    }
}

And that’s it!

What do you get with the above in Gradle 6.7?

You get:

  • All Java compilation tasks will use Java 11 to build. That means code in the main and test source sets, but also Java code in any custom source set you add, will be built with the configured Java version.
  • All tests tasks, including the default test task and any additional custom Test task, will use Java 11 to run the tests.
  • The javadoc task will use Java 11 to build the documentation.

In addition, with the application plugin, the run task will use Java 11 to start your application.

Behind the scenes, Gradle will have:

If Gradle cannot find the requested Java version, you can tell it where to look.

Task level configuration #

In some cases, you need to use a different toolchain for a specific task. Let’s say the library you develop has a specific code path depending on the Java version it runs on. You will want to make sure both code paths are exercised and so will run a set of tests with a different Java version. This scenario is supported as follows.

tasks.register<Test>("extraTests") {
    javaLauncher.set(javaToolchains.launcherFor {
        languageVersion.set(JavaLanguageVersion.of(14))
    })
}

Head over to the documentation to learn more about it.

Further benefits of toolchain usage #

In addition to the benefits we have discussed earlier, toolchains can help in more situations.

It was problematic for Gradle to build code on not yet released Java versions – like Java 16 at the time of writing – which may fail to run existing releases of Gradle – such as the current 6.7 release.

With toolchains support, Gradle 6.7 is perfectly capable of compiling and testing code with a not yet released language version. You only need to ask it to do it:

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(16))
    }
}

Additionally, the code may behave differently when built and run with different Java versions, potentially causing hard to diagnose issues.

Even without incompatibilities affecting the system behavior, using different Java versions to build the project on different machines can reduce build performance because of build cache misses.

Relaxing Java version constraints on Gradle #

In addition to the benefits for build authors, this feature will have an impact on the development of Gradle itself. Up to now Gradle assumed that it would run with the same version of Java as required by your project. This means that Gradle has to be able to run with a set of Java versions spanning the needs of the projects using Gradle. With the release cadence of Java 5 to 8 this was probably acceptable. With the new Java release schedule, this is more problematic.

As of version 6.7, Gradle requires:

  • Java 8 at the minimum to run a build,
  • is built daily with 11
  • and tested up to Java 15.

All of that requires quite a sophisticated CI setup and imposes constraints on Gradle core development. Features and APIs beyond Java 8 are not accessible to Gradle engineers for developing the tool for example.

Once toolchains are adopted in the community, we will eventually be able to be less conservative with Gradle’s runtime requirements. Gradle may then only run on relatively recent Java versions while supporting to build code for older Java versions through toolchains.

What’s next for toolchains? #

In Gradle 6.7, the toolchain support is limited to the tasks known by the Java dedicated plugins:

  • JavaCompile
  • Test
  • Javadoc
  • JavaExec

Toolchain support for the Groovy and Scala plugins is planned to be added in one of the next Gradle releases.

We also hope that given the benefits of this feature for users, community plugin authors will leverage the toolchain support in their own tasks.

Discuss