skip to content

Toolchains in Android Builds

Using JVM toolchains with the Android Gradle Plugin and the Kotlin jvmToolchain setting. Comes up because AGP's compile options and toolchains interact in ways that routinely trip people up.

on this pageshow

questions

5

How do you tell an Android Gradle Plugin (AGP) project which JDK to use to compile Java and Kotlin, using a JVM toolchain?

level: juniorimportance: must knowfreq 55%

answer

  1. jvmToolchain(17)
  2. decouples Gradle JDK from compile JDK
  3. java { toolchain { languageVersion } }
  4. AGP applies to JavaCompile
  5. reproducible builds

basics

~10 s

Set a JVM toolchain language version. With the Kotlin plugin use kotlin { jvmToolchain(17) }; AGP picks up that JDK to compile Java and Kotlin instead of the JDK running Gradle.

solid answer

~30 s

A JVM toolchain decouples the JDK that *runs* Gradle from the JDK that *compiles* your code. In an AGP project the simplest declaration is `kotlin { jvmToolchain(17) }` (Kotlin Gradle Plugin), which Gradle resolves to a real JDK 17 install (auto-provisioning it if a resolver is configured). The Kotlin plugin propagates that toolchain to Kotlin compilation, and AGP aligns Java compilation and `compileOptions` source/target to it. You can also configure `java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }` directly. The benefit: the build is reproducible and independent of whatever JDK the developer or CI machine happens to launch Gradle with.

code

kotlin · 11 lines
kotlin
// app/build.gradle.kts
kotlin {
    jvmToolchain(17)
}

// equivalent low-level form:
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

go deeper

for a junior

Know the one-liner kotlin { jvmToolchain(17) } and that it picks the JDK used to compile.

for a middle

Explain the Gradle-JDK vs toolchain-JDK distinction and that the Kotlin plugin propagates the toolchain to both Java and Kotlin.

for a senior

Discuss reproducibility, auto-provisioning fallback, and how AGP's JavaCompile inherits the toolchain language version.

for a principal

Standardize toolchain language versions across an Android monorepo so every module and CI agent compiles identically regardless of the launching JDK.

## What a JVM toolchain is Gradle separates two JDKs: - the **Gradle JDK** — the JVM that launches and runs the Gradle daemon; - the **toolchain JDK** — the JVM used to *compile* and (optionally) test your code. A **JVM toolchain** lets you declare the second one by *language version* (`17`, `21`, …) rather than hard-coding a path. Gradle then locates a matching JDK on the machine (or downloads one if auto-provisioning is enabled). This makes the build reproducible regardless of which JDK started Gradle. ## Declaring it in an AGP build There are two common entry points: 1. **Via the Kotlin Gradle Plugin** (recommended in Android, since most modules are Kotlin): `kotlin { jvmToolchain(17) }`. This is sugar over the Java toolchain — it sets the same `JavaLanguageVersion` and wires it into Kotlin compilation. 2. **Via the `java` extension directly:** `java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }`. ## How AGP consumes it AGP reads the project's Java toolchain and applies its language version to `JavaCompile` tasks. When the Kotlin plugin and AGP agree on the same toolchain, both Java and Kotlin compile against JDK 17, and `compileOptions.sourceCompatibility/targetCompatibility` default to the toolchain language version unless overridden. ## Why prefer a toolchain over `JAVA_HOME` - Reproducible: CI and laptops compile with the same JDK. - The Gradle daemon can keep running on a newer JDK (e.g. 21) while still producing JDK-17 bytecode. - It plays with Foojay auto-provisioning so missing JDKs download automatically. ```kotlin // app/build.gradle.kts plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } android { compileSdk = 34 // compileOptions left to inherit the toolchain language version } kotlin { jvmToolchain(17) // one line: Java + Kotlin compile on JDK 17 } ```

  • Does `jvmToolchain(17)` configure both Java and Kotlin compilation?
    Yes. The Kotlin plugin sets the Java toolchain language version and wires Kotlin compilation to the same JDK, so both `JavaCompile` and Kotlin compile tasks run on JDK 17.
  • What happens if no JDK 17 is installed locally?
    Gradle fails to resolve the toolchain unless auto-provisioning (e.g. the Foojay resolver plugin) is configured, in which case it downloads a matching JDK.

The Gradle JDK is the kitchen you cook in; the toolchain JDK is the recipe's required oven temperature — you fix the temperature so the dish comes out the same no matter whose kitchen runs it.

saying these in an interview costs you the question

  • Claiming the toolchain changes which JDK runs the Gradle daemon — it only changes the compile JDK.
  • Confusing toolchain *language version* with `compileSdk`/`targetSdk` (Android API levels).

context

open as a page

How do AGP's `compileOptions.sourceCompatibility`/`targetCompatibility` relate to a JVM toolchain, and which one should you set?

level: middleimportance: must knowfreq 50%

basics

~10 s

The toolchain selects which JDK compiles; compileOptions sets the Java bytecode level (source/target). With a toolchain set, source/target default to the toolchain version, so you usually only need the toolchain.

open as a page

What does the Kotlin Gradle Plugin's `jvmToolchain()` actually do under the hood in an Android module, and how does it differ from the raw `java.toolchain` block?

level: middleimportance: should knowfreq 35%

basics

~20 s

jvmToolchain(17) is a Kotlin-plugin convenience that sets the Java toolchain language version and wires Kotlin compile tasks to the same JDK. The raw java.toolchain block only configures Java; Kotlin needs the plugin to follow it.

open as a page

An AGP module has no toolchain declared and no compileOptions set. What JDK compiles the code and what bytecode is emitted — and why is that a problem?

level: middleimportance: should knowfreq 28%

basics

~10 s

Without a toolchain, AGP compiles using the JDK that runs Gradle (the daemon JDK), and defaults to a low source/target (historically Java 8). That's a problem because output depends on whoever's machine launched Gradle.

open as a page

You run CI on a JDK 21 agent but ship Android bytecode that must build identically everywhere. How do toolchains let the Gradle daemon stay on JDK 21 while AGP compiles on a fixed JDK?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Declare a fixed toolchain (e.g. jvmToolchain(17)). The daemon keeps running on JDK 21, but Gradle resolves JDK 17 for AGP's JavaCompile and Kotlin compile, so output is identical regardless of the launching JDK.

open as a page