skip to content

When a toolchain is requested, how does Gradle decide between a local JDK and provisioning a new one?

level: seniorimportance: should knowfreq 32%

answer

  1. detect first, provision second
  2. many detection sources incl. ~/.gradle/jdks
  3. vendor/impl can exclude a version-match
  4. auto-download=false => fail fast
  5. ./gradlew javaToolchains to debug

basics

~10 s

Gradle always prefers a matching locally installed JDK. It scans known locations first; only if nothing matches the requested version/vendor/implementation does it provision via a resolver — and only if auto-download isn't disabled.

solid answer

~40 s

The selection order is: **detect, then provision**. For a toolchain spec (language version + optional vendor + implementation), Gradle first enumerates **local installations** — from auto-detected locations (SDKMAN, asdf, OS package dirs, `JAVA_HOME`, Gradle's own provisioned cache) plus any in `org.gradle.java.installations.paths`. If one satisfies the spec, it's used; no network call happens. Only when **no local match** exists does Gradle consult the registered **toolchain resolvers** (foojay) to download one — and even then only if `org.gradle.java.installations.auto-download` is not `false`. Previously provisioned JDKs in `~/.gradle/jdks` count as 'local' on later builds, so a given (version, vendor) is fetched at most once. This local-first ordering means adding the foojay plugin never overrides a perfectly good installed JDK; it's a strict fallback.

code

bash · 5 lines
bash
# Inspect what Gradle detected and from which source
./gradlew -q javaToolchains

# Forces use of only explicitly listed JDKs (no auto-detection)
./gradlew build -Dorg.gradle.java.installations.auto-detect=false

go deeper

for a junior

Know local JDKs are preferred and download is the fallback.

for a middle

List detection sources and the auto-download gate; know provisioning is one-time per spec.

for a senior

Explain how vendor/implementation constraints can override a version match and how to debug with javaToolchains.

for a principal

Design controlled environments (auto-detect off + explicit paths, or download off) for deterministic, auditable toolchain selection.

## Two phases: detection then provisioning When a task needs a toolchain (compiler, launcher, javadoc tool) for a given **spec** — language version, and optionally vendor and implementation (HotSpot vs J9) — Gradle resolves it in a fixed order. ### Phase 1 — local detection Gradle builds a list of **installed JDKs** from multiple sources: - OS-conventional install directories (e.g. `/usr/lib/jvm`, macOS `/Library/Java/JavaVirtualMachines`). - Version managers: **SDKMAN**, **asdf**, **jabba**, Intellij's JDKs. - `JAVA_HOME` and the JVM running Gradle itself. - Gradle's **own provisioning cache** `~/.gradle/jdks` (JDKs it downloaded earlier). - Explicit `org.gradle.java.installations.paths=/opt/jdk-21,...`. It then filters by the spec. If at least one matches, Gradle picks it (preferring exact/closest match) and **stops** — no resolver is consulted, no network used. ### Phase 2 — provisioning (fallback only) If **no** local installation matches, Gradle moves to provisioning: 1. Check `org.gradle.java.installations.auto-download`. If `false`, **fail fast** — no download. 2. Otherwise, ask each registered **toolchain resolver** (in declared order) for a download. The foojay resolver hits the Disco API for a matching build. 3. Download, verify, unpack into `~/.gradle/jdks`, and use it. Because the cache directory is itself a detection source, the **next** build with the same spec finds it locally in Phase 1 — provisioning is a one-time cost per (version, vendor, implementation). ## Practical consequences - **Adding foojay is non-intrusive.** A developer with JDK 21 already installed never triggers a download; the plugin only helps when something is genuinely missing. - **CI determinism.** Bake JDKs into the image so Phase 1 always succeeds; optionally set `auto-download=false` so Phase 2 can never silently fire. - **Diagnosing 'why did it download?'** Run `./gradlew javaToolchains` to print every detected installation and its source — usually the requested version simply wasn't among them, or a vendor/implementation constraint excluded it. ```bash ./gradlew -q javaToolchains ``` ## Edge cases - A **vendor or implementation** constraint can exclude an otherwise version-matching local JDK, forcing provisioning even though 'a JDK 21' is installed. - `auto-detect` can be turned off (`org.gradle.java.installations.auto-detect=false`) to consider only explicit paths — useful for fully controlled environments.

  • A developer has JDK 21 installed but Gradle still downloads one — why?
    A vendor or implementation constraint in the toolchain spec likely excluded the installed JDK 21, so the version match wasn't sufficient and provisioning fired. `./gradlew javaToolchains` shows the detected installs.
  • How is a previously downloaded JDK reused?
    Gradle's provisioning cache `~/.gradle/jdks` is itself a detection source, so subsequent builds find it during local detection and skip provisioning.

saying these in an interview costs you the question

  • Saying Gradle prefers downloading over using a local JDK.
  • Forgetting that vendor/implementation constraints can force provisioning despite a matching version.

context