skip to content

How do you make a single JVM test suite run on a specific JDK using a Java toolchain, and what does that actually do at execution time?

level: middleimportance: must knowfreq 45%

answer

  1. javaToolchains.launcherFor { languageVersion = ... }
  2. set Test.javaLauncher on the target
  3. JavaToolchainService: compilerFor/launcherFor/javadocToolFor
  4. forks JVM with chosen JDK at run time
  5. overrides project java.toolchain for one suite

basics

~10 s

Set a JavaLanguageVersion on the target's Test task: testTask.configure { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) } }. Gradle then runs that suite's tests on JDK 21, independent of the build JVM.

solid answer

~40 s

A Java *toolchain* lets a suite run on a JDK that may differ from the JDK running Gradle. You give the suite's `Test` task a `JavaLauncher` selected by `JavaLanguageVersion`. Inside the target you write `testTask.configure { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) } }`, where `javaToolchains` is the `JavaToolchainService`. At execution Gradle locates an installed JDK 21 (or downloads it via a toolchain resolver / auto-provisioning), and forks the test JVM with that JDK's `java` launcher. This is how you run, say, your `integrationTest` suite on JDK 21 while the main build and unit tests stay on JDK 17 — without changing the project-wide `java.toolchain`. Because it's per-target, a multi-target suite can run the same tests on several JDKs.

code

kotlin · 16 lines
kotlin
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            useJUnitJupiter()
            targets {
                all {
                    testTask.configure {
                        javaLauncher = javaToolchains.launcherFor {
                            languageVersion = JavaLanguageVersion.of(21)
                        }
                    }
                }
            }
        }
    }
}

go deeper

for a junior

Recall that you set javaLauncher via javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(N) } to run a suite on a specific JDK.

for a middle

Explain JavaToolchainService, launcher vs compiler, that resolution/forking happen at execution, and that this overrides the project toolchain for one suite only.

for a senior

Cover compile-vs-run distinction, auto-provisioning via download repositories, toolchain as a task input affecting caching, and multi-target JDK matrices.

for a principal

Discuss org-wide toolchain governance: pinning vendors/versions, provisioning policy, and reproducibility across CI agents and developer machines.

## What a Java toolchain is A **Java toolchain** decouples *the JDK that runs Gradle* from *the JDK used to compile and run your code*. You declare a requirement ("I need JDK 21") as a `JavaLanguageVersion`, and Gradle's **`JavaToolchainService`** resolves it to a concrete installed JDK — detecting local installations, honoring `org.gradle.java.installations.*` properties, or auto-provisioning via a toolchain download repository (e.g. Foojay resolver). This makes builds reproducible across machines regardless of the developer's `JAVA_HOME`. The service exposes three providers: - `compilerFor { }` -> `JavaCompiler` (for `JavaCompile` tasks) - `launcherFor { }` -> `JavaLauncher` (for `Test`, `JavaExec`, application run) - `javadocToolFor { }` -> `JavadocTool` ## Applying a toolchain to one suite To make a *single* test suite run on a chosen JDK you set the `Test` task's `javaLauncher` through the suite's target: ```kotlin testing { suites { val integrationTest by registering(JvmTestSuite::class) { useJUnitJupiter() targets { all { testTask.configure { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) } } } } } } } ``` `javaToolchains` is the `JavaToolchainService` instance the Java plugin adds to the project. `launcherFor { }` returns a `Provider<JavaLauncher>`; assigning it to `Test.javaLauncher` tells Gradle to fork the test JVM with **that** JDK's `java` binary. ## What happens at execution time 1. During configuration the requirement (`JavaLanguageVersion.of(21)`) is recorded — nothing is resolved yet. 2. When the `integrationTest` task is about to run, Gradle resolves a matching JDK from detected installations; if none is found and auto-provisioning is enabled, it downloads one. 3. Gradle **forks** a new JVM using that JDK's launcher and runs the test classes there. The build JVM (which may be JDK 17) is untouched. 4. The chosen JDK is cached, so subsequent runs are fast and the choice is part of the task's inputs (affecting up-to-date / build-cache keys). ## Project-wide vs. per-suite - Project-wide: `java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }` sets compile + test + run for the whole project. - Per-suite override: setting `javaLauncher` on just one suite's `testTask` overrides the launcher for that suite only — the rest of the build keeps the project toolchain. This is the canonical "unit tests on 17, integration tests on 21" setup, or matrix-testing one suite across multiple JDKs via multiple targets. ## Gotchas - Setting only the **launcher** affects how tests *run*, not how the suite's classes are *compiled*. If the suite's bytecode must also target a specific release, set the matching `JavaCompile` task's `javaCompiler` (or `options.release`) too. - The requested JDK must be resolvable; without it installed and without a download repository configured, the task fails at execution with a toolchain-resolution error.

  • If you set only javaLauncher, are the suite's test classes also compiled with JDK 21?
    No. javaLauncher controls only the runtime JVM. Compilation uses the JavaCompile task's compiler (the project toolchain by default). To compile with 21 too, set javaCompiler on the suite's JavaCompile task or set options.release.
  • What happens at execution if JDK 21 is not installed?
    Gradle tries to auto-provision it from a configured toolchain download repository (e.g. the Foojay resolver plugin). If none is configured and no matching JDK is detected, the task fails with a toolchain resolution error.
  • Where does javaToolchains come from?
    It is the JavaToolchainService, registered on the project by the java plugin; it exposes compilerFor, launcherFor, and javadocToolFor.

saying these in an interview costs you the question

  • Saying a per-suite toolchain changes the JVM that runs Gradle itself — it only forks the test JVM.
  • Assuming launcherFor also recompiles the tests with that JDK.
  • Believing the JDK is resolved at configuration time rather than execution time.

context