Across many repositories and a CI fleet, how would you standardize and govern the JVM that runs Gradle (the daemon JVM) without breaking individual contributors?
answer
- criteria file in every repo (templated)
- wrapper + criteria = deterministic bootstrap
- provisioning repos / internal JDK mirror
- ban committed org.gradle.java.home
- daemon JVM ≠ per-project toolchain
basics
~10 sCommit a Daemon JVM Criteria file per repo to pin version/vendor, configure toolchain provisioning/repositories for CI, ban committed org.gradle.java.home paths, and allow personal overrides only in ~/.gradle. This gives reproducibility while keeping local flexibility.
solid answer
~50 sAt fleet scale, the goal is **reproducible daemon-JVM selection** without depending on each machine's `JAVA_HOME`. The backbone is a committed **Daemon JVM Criteria** file (`gradle-daemon-jvm.properties`) in every repo, pinning a standard version and vendor — generated and updated via `updateDaemonJvm`, ideally enforced by a convention/template or a lint check in CI. Pair it with the wrapper so 'Gradle version + JVM that runs Gradle' are both version-controlled. For machines lacking the JDK, configure **toolchain provisioning** (toolchain download repositories / a managed JDK mirror) so resolution succeeds offline-of-the-internet or behind a proxy. Governance rules: forbid absolute `org.gradle.java.home` in committed `gradle.properties` (it's non-portable); permit it only in `~/.gradle/gradle.properties` as a personal escape hatch. In CI, pin the JVM explicitly via the criteria file rather than the runner image's ambient default, and fail fast if `--version` reports an off-policy JVM. This separates the org-standard daemon JVM from per-project build toolchains, so platform upgrades roll out centrally while teams keep autonomy over their compile targets.
code
bash · 5 lines# Roll the org-standard daemon JVM across a repo
./gradlew updateDaemonJvm --jvm-version=17 --jvm-vendor=adoptium
# CI guardrail: fail if the daemon isn't on the policy JVM
./gradlew --version | grep -q 'JVM:.*17' || { echo 'Off-policy daemon JVM'; exit 1; }go deeper
Recognize that pinning the daemon JVM in a committed file gives reproducibility — details not expected.
Describe using the criteria file + wrapper together and avoiding committed absolute paths.
Add provisioning, CI assertions, and the daemon-vs-toolchain separation.
Own the full policy: templates, guardrails, provisioning supply chain, central upgrades, and a personal-override escape hatch.
## Governing the daemon JVM at org scale ### Why this is a platform concern The JVM that runs Gradle (the **daemon JVM**) is invisible until it drifts: a contributor on a new JDK hits an unsupported-version error, or CI silently differs from laptops. Multiply across dozens of repos and you get sporadic, hard-to-reproduce build failures. Treating daemon-JVM selection as **governed, version-controlled config** removes that class of failure. ### Pillar 1 — committed Daemon JVM Criteria everywhere Standardize on `gradle/gradle-daemon-jvm.properties` in every repo: ```properties toolchainVersion=17 toolchainVendor=ADOPTIUM ``` Generate via `./gradlew updateDaemonJvm --jvm-version=17 --jvm-vendor=adoptium`. Because it's committed, a fresh clone resolves the same daemon JVM everywhere. Bake it into repo templates / cookiecutters so new repos start compliant. ### Pillar 2 — pair with the wrapper The wrapper pins the **Gradle version**; criteria pins the **JVM Gradle runs on**. Together they make `git clone && ./gradlew build` deterministic. Upgrade them together when you bump the platform baseline. ### Pillar 3 — provisioning & supply chain Machines without the target JDK need a resolution path: - Configure **toolchain download repositories** (or a foojay-style resolver) so Gradle can fetch a matching JDK. - For locked-down networks, host an internal JDK mirror and point provisioning at it. This keeps offline/proxied CI agents able to resolve the criteria. ### Pillar 4 — guardrails (what to forbid) - **No committed `org.gradle.java.home`** with absolute paths in project `gradle.properties` — it's machine-specific and breaks everyone else. Enforce via a CI lint/grep or a settings convention plugin that warns. - Allow `org.gradle.java.home` only in **`~/.gradle/gradle.properties`** as a personal override (e.g. a contributor on an unusual platform). ### Pillar 5 — CI specifics - Don't rely on the runner image's ambient `JAVA_HOME`; let the committed criteria choose, or provision explicitly. - Add a fast check that `./gradlew --version` reports the policy JVM; fail the pipeline if not. - Cache provisioned JDKs to avoid re-downloading per build. ### Pillar 6 — separation from build toolchains Crucially, the org-standard **daemon JVM** is independent of each project's **build toolchain**. A team can target Java 21 bytecode (toolchain) while the whole org runs Gradle on a stable Java 17 daemon. This lets the platform team upgrade the daemon JVM baseline centrally without forcing every team to change their compile target — and vice versa. ### Rollout strategy 1. Define the standard (version + vendor). 2. Template it into new repos; script `updateDaemonJvm` across existing ones. 3. Configure provisioning/mirrors. 4. Add CI guardrails (forbid committed java.home, assert the daemon JVM). 5. Document the personal-override escape hatch.
- Why keep the daemon JVM standard separate from each project's build toolchain?So the platform team can upgrade the JVM that runs Gradle centrally without dictating every team's compile target — and teams can change bytecode levels without touching the daemon JVM.
- How do you let an individual contributor override the standard without breaking the repo?Permit org.gradle.java.home only in their personal ~/.gradle/gradle.properties; never in committed project config. The repo stays portable; their machine is overridden locally.
- What enables daemon-JVM provisioning to work on locked-down CI?Configured toolchain download repositories or an internal JDK mirror, plus a cache, so the criteria resolves without uncontrolled internet access.
saying these in an interview costs you the question
- Standardizing via committed absolute org.gradle.java.home paths — non-portable across the fleet.
- Conflating the daemon JVM standard with build toolchain versions (they should be governed independently).
- Relying on CI runner images' ambient JAVA_HOME instead of an explicit, version-controlled selection.