skip to content

As a lead managing many repos, how do you govern Gradle/JDK/plugin compatibility so a JDK or Gradle upgrade doesn't break builds across the org?

level: principalimportance: nice to knowfreq 25%

answer

  1. known-good quad: Gradle+JDK+KGP+AGP
  2. distribute via version catalog + convention plugins
  3. pin daemon JDK and toolchain
  4. staged canary-then-fanout wave
  5. drift-detection CI guardrail

basics

~10 s

Standardize a matrix-vetted set (Gradle + run-on JDK + Kotlin/AGP), share it via version catalogs/convention plugins, pin daemon JDKs and toolchains, and roll upgrades out in a staged, tested wave.

solid answer

~50 s

At org scale, compatibility is a governance problem, not a per-repo fix. Define a **known-good quad** — Gradle version, run-on JDK, Kotlin plugin, AGP — that all intersect per the official matrices, and treat it as a paved road. Distribute it: a shared **version catalog** (libs.versions.toml) for plugin versions, **convention plugins** (a published `build-logic`/settings plugin) that set the toolchain and daemon expectations, and a pinned wrapper version everyone inherits. Pin the **daemon JDK** explicitly (so CI images and dev machines agree) and set **toolchains** so the compile target is uniform and reproducible. For upgrades, run a staged wave: bump the shared definition on a canary repo, run full CI, then fan out; check the matrix for the run-on JDK before changing CI images. Add guardrails: a CI check that fails if a repo drifts from the sanctioned versions, and read the deprecation/warning output (a separate concern) to pre-empt the *next* bump. The goal is that no single team discovers an incompatibility in production CI.

code

toml · 8 lines
toml
# gradle/libs.versions.toml — one sanctioned, matrix-vetted set, shared org-wide
[versions]
kotlin = "2.0.0"
agp = "8.5.0"

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
android-app = { id = "com.android.application", version.ref = "agp" }

go deeper

for a junior

Out of scope; just know upgrades should be coordinated, not ad hoc.

for a middle

Mention version catalogs and pinning the wrapper/toolchain for consistency.

for a senior

Describe the known-good set and staged rollout with full-CI canary.

for a principal

Full governance: paved-road quad, distribution mechanism, pinned JDKs, drift guardrails, wave rollout.

## Governing compatibility across many repos For one project you read the matrix and pick versions. Across dozens of repos, you need *governance* so an upgrade is predictable. ### 1. Define a known-good quad Using the official compatibility matrices, choose an intersecting set: - **Gradle version** (and its run-on JDK range) - **Daemon/run-on JDK** (a supported LTS within that range) - **Kotlin Gradle Plugin** version (supports that Gradle) - **AGP** version, if Android (min Gradle + required JDK) This quad is your paved road. Every repo on it is, by construction, compatible. ### 2. Distribute it, don't copy-paste - **Version catalog** (`gradle/libs.versions.toml`) centralizes plugin versions; repos reference aliases. - **Convention/settings plugins** (a `build-logic` included build or a published settings plugin) set the toolchain `languageVersion`, vendor, and shared config so individual repos don't redefine compatibility-sensitive settings. - **Wrapper version** is inherited from a template; pin it. ```kotlin // build-logic convention plugin applied everywhere java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } ``` ### 3. Pin both JDKs - **Daemon JDK**: pin it in CI images and document it for dev machines so `./gradlew --version` is uniform and within the matrix's run-on range. - **Toolchain**: pin language version (+vendor) for a reproducible compile target. ### 4. Stage upgrades as a wave 1. Update the shared quad on a **canary** repo. 2. Run full CI (not just the fast gate) — including the matrix recheck for any new run-on JDK. 3. Fan out to the rest once green. 4. Update CI base images only after confirming the wrapper's Gradle supports the new daemon JDK. ### 5. Guardrails - A CI check that fails the build if a repo's Gradle/plugin/JDK drifts from the sanctioned quad. - Monitor deprecation output to schedule the *next* upgrade before it becomes forced. ### Why this matters The failure mode without governance is each team independently bumping a JDK or Gradle, hitting a matrix or plugin-range incompatibility, and discovering it as red CI in production pipelines. A centrally vetted, distributed, staged process turns compatibility from a recurring surprise into a controlled rollout.

  • How do you stop individual repos from drifting off the sanctioned versions?
    A CI guardrail check that compares the repo's Gradle/plugin/JDK against the sanctioned quad and fails on drift, plus convention plugins that centralize the settings.
  • Why canary one repo before fanning out an upgrade?
    To surface matrix/plugin incompatibilities in a controlled place with full CI before they hit every team's pipeline.

saying these in an interview costs you the question

  • Letting each team bump JDK/Gradle independently with no central matrix-vetted set.
  • Relying on copy-pasted build config instead of shared convention plugins/catalogs.

context