skip to content

What does `kotlin { jvmToolchain(21) }` do, and how is it different from setting jvmTarget?

level: middleimportance: must knowfreq 65%

answer

  1. Toolchain = which JDK runs the build
  2. jvmTarget = which bytecode version kotlinc emits
  3. jvmToolchain aligns Kotlin+Java+test JDK
  4. Reproducible, independent of JAVA_HOME
  5. Foojay resolver enables auto-download

basics

~20 s

jvmToolchain(21) tells Gradle to compile and run with a JDK 21, downloading one if needed. jvmTarget only sets the bytecode version. The toolchain controls which JDK runs the build; the target controls what class-file version is produced.

solid answer

~40 s

`kotlin { jvmToolchain(21) }` configures the **Gradle Java toolchain** for both Kotlin and Java compilation: Gradle locates (or provisions, via a download repository) a JDK whose major version is 21 and uses it for `compileKotlin`, `compileJava`, and the JDK that runs tests. It also implicitly sets the Kotlin `jvmTarget` and Java `release` to 21, so the produced bytecode matches. By contrast, setting only `compilerOptions { jvmTarget = JvmTarget.JVM_21 }` changes *only* the target bytecode version emitted by kotlinc; it does not control which JDK actually runs the compiler. Using a toolchain makes the build **reproducible across machines** regardless of the developer's local `JAVA_HOME`. You can still override the bytecode target separately from the toolchain JDK (e.g. toolchain 21 but jvmTarget 17) if you must run on a newer JDK but emit older bytecode.

code

kotlin · 7 lines
kotlin
// Build on JDK 21, but emit bytecode compatible with JVM 17
kotlin {
    jvmToolchain(21)
    compilerOptions {
        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
    }
}

go deeper

for a junior

Knows jvmToolchain(21) picks JDK 21 and is configured in the kotlin block.

for a middle

Clearly separates toolchain (runtime JDK) from jvmTarget (bytecode), and knows the toolchain aligns Java and tests too.

for a senior

Explains reproducibility vs JAVA_HOME, the Foojay auto-download mechanism, and valid toolchain-21/target-17 splits.

for a principal

Reasons about org-wide toolchain policy, vendor pinning, provisioning in CI, and emitting older bytecode while building on a current secure JDK.

## The two independent knobs There are two distinct concepts people confuse: - **Toolchain** = *which JDK runs the build* (the `javac`/`kotlinc` launcher and the test JVM). - **jvmTarget** = *which bytecode version kotlinc emits* (the `version` field in the produced `.class` files). ## jvmToolchain ```kotlin kotlin { jvmToolchain(21) } ``` This is sugar over Gradle's **Java Toolchains** feature. It: - Asks Gradle for a JDK with **language version 21**. If none is installed, and a toolchain **download repository** (the Foojay resolver plugin) is configured in `settings.gradle.kts`, Gradle downloads one automatically. - Applies that JDK to **Kotlin compilation, Java compilation, and the test runtime** consistently. - Implicitly aligns the **Kotlin `jvmTarget`** and the **Java `release`** to 21. The big win is **reproducibility**: the build no longer depends on whatever `JAVA_HOME` a developer happens to have. For finer control you can configure the underlying spec: ```kotlin kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(21)) vendor.set(JvmVendorSpec.ADOPTIUM) } } ``` ## jvmTarget ```kotlin kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } } ``` This only sets the **bytecode version**. It does **not** choose the JDK. So `jvmTarget = JVM_17` with no toolchain means: compile with whatever JDK is running Gradle, but emit JDK 17-compatible class files. ## When they diverge A legitimate combination is **toolchain 21 + jvmTarget 17**: build on a modern, secure JDK 21 but ship bytecode that runs on a JVM 17 production runtime. The toolchain decides the compiler/runtime; jvmTarget decides the emitted class-file level. If you only set the toolchain, jvmTarget follows it automatically. ## Settings needed for auto-download ```kotlin // settings.gradle.kts plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } ``` Without that resolver, an uninstalled toolchain fails the build instead of being downloaded.

  • Your CI machine only has JDK 17 but the build needs toolchain 21 — what happens?
    Gradle tries to locate a JDK 21. If the Foojay toolchain resolver is configured it auto-downloads one; otherwise the build fails with a 'No matching toolchains' error.
  • If you set jvmToolchain(21) and nothing else, what is the jvmTarget?
    It is implicitly 21 — the toolchain aligns the Kotlin jvmTarget (and Java release) to the toolchain's language version.

The toolchain is the oven you bake in; jvmTarget is the 'best before' label on the cake — different ovens can still print the same label.

saying these in an interview costs you the question

  • Saying jvmTarget controls which JDK runs the build
  • Claiming the toolchain only affects Kotlin and not Java/tests
  • Believing toolchain JDK and bytecode target must always be identical
  • Not knowing JAVA_HOME is bypassed when a toolchain is configured

context