How would you govern toolchain auto-provisioning across an organization for security and reproducibility?
answer
- tiered: dev convenience vs CI lockdown
- pre-bake JDKs + auto-download=false
- custom resolver -> internal mirror
- settings convention plugin for org policy
- pin version+vendor+impl, attest in build scan
basics
~20 sPre-provision JDKs in build images so foojay isn't hit at build time, disable auto-download in CI, optionally point a resolver at an internal mirror instead of foojay, and pin/verify the JDKs so builds stay reproducible and auditable.
solid answer
~40 sTreat the JDK as a controlled build input. For **developers**, keep the foojay convention plugin for convenience — fast onboarding without manual JDK installs. For **CI/production builds**, lock it down: bake the exact JDKs into the build image so local detection always succeeds, and set `org.gradle.java.installations.auto-download=false` so a build can never silently fetch a binary from an external API. If you still want on-demand provisioning but not from the public foojay endpoint, implement or configure a **custom toolchain resolver** that points at an **internal artifact mirror** (your Nexus/Artifactory hosting vetted JDK builds), registered ahead of foojay in `toolchainManagement`. This gives reproducibility (pinned, checksummed JDKs), supply-chain control (no unvetted runtime downloads), offline/air-gapped support, and auditability. Standardize all this via a **settings convention plugin** so every repo inherits the same policy.
code
properties · 4 lines# CI gradle.properties enforced by the build image
org.gradle.java.installations.auto-download=false
org.gradle.java.installations.auto-detect=false
org.gradle.java.installations.paths=/opt/corp-jdk-17,/opt/corp-jdk-21go deeper
Recognize that downloading JDKs at build time has security implications; defer the policy to seniors.
Know you can disable downloads in CI and pre-install JDKs; mention internal mirrors exist.
Design the dev-vs-CI tiering and describe a custom resolver against an internal mirror.
Own org-wide policy: settings convention plugin, pinned/attested toolchains, supply-chain and air-gapped considerations, and the trade-offs of each approach.
## Why governance matters here Auto-provisioning makes a **build-time network fetch of an executable binary** — exactly the kind of thing supply-chain security programs scrutinize. Left unmanaged, two builds of the same commit could use different JDK patch releases, or pull from an endpoint outside the org's control. Governance reconciles developer convenience with production rigor. ## A tiered policy **Tier 1 — developer machines:** keep `foojay-resolver-convention`. Onboarding is `git clone && ./gradlew build`; missing JDKs appear automatically. Convenience outweighs lockdown locally. **Tier 2 — CI:** make toolchains deterministic. - Pre-install the exact JDKs in the build image so **local detection** (Phase 1) always satisfies the spec. - Set `org.gradle.java.installations.auto-download=false` (and often `auto-detect=false` with explicit `installations.paths`) so the build **cannot** fetch or pick up a stray JDK. - Result: identical, offline, audit-friendly builds. **Tier 3 — controlled provisioning (optional):** if you want on-demand JDKs without the public foojay endpoint, register a **custom `JavaToolchainResolver`** (the same SPI foojay implements) that resolves against an **internal mirror** of vetted, checksummed JDK builds. Register it in `toolchainManagement.jvm.javaRepositories` ahead of (or instead of) foojay. ```kotlin // settings.gradle.kts (or a settings convention plugin) toolchainManagement { jvm { javaRepositories { repository("corp-mirror") { resolverClass = com.acme.gradle.CorpToolchainResolver::class.java } // foojay as fallback for dev only, gated by an env check if desired } } } ``` ## Standardize via a settings convention plugin Don't copy-paste policy into every repo. Publish a **settings plugin** that applies the right `toolchainManagement` config and properties based on environment (e.g. detects CI). Every repo applies one plugin and inherits org policy — centralizing future changes (new mirror URL, new pinned versions). ## Reproducibility & auditability levers - **Pin** language version *and* vendor *and* implementation in the toolchain spec so the same build always selects the same JDK family. - Source JDKs from a **mirror with integrity checks** so the binary is vetted and stable. - Record the selected toolchain in build scans / provenance metadata for SLSA-style attestation. - Disable downloads in CI so provenance can't be undermined by an opportunistic fetch. ## Trade-offs to articulate - Pre-provisioned images = larger images and an image-maintenance burden, but maximal determinism. - Internal resolver = engineering investment, but full control and air-gapped support. - Public foojay everywhere = simplest, but cedes sourcing control and adds a network dependency to every fresh agent.
- How do you provide on-demand JDKs without depending on the public foojay endpoint?Implement a custom JavaToolchainResolver (same SPI foojay uses) that resolves against an internal artifact mirror of vetted, checksummed JDKs, and register it in toolchainManagement ahead of foojay.
- How do you keep this policy consistent across hundreds of repos?Publish a settings convention plugin that applies the toolchainManagement config and properties; each repo applies one plugin and inherits org-wide policy, so changes are made centrally.
- What's the trade-off of pre-provisioning JDKs in CI images?Maximal determinism and no build-time fetch, at the cost of larger images and ongoing image maintenance to add/update JDK versions.
saying these in an interview costs you the question
- Recommending public foojay for production builds with no controls or fallback story.
- Treating the JDK download as not part of the supply chain.
- Hand-editing toolchainManagement in every repo instead of a shared convention plugin.