skip to content

How would you centrally pin or override a plugin version for all build scripts using resolutionStrategy?

level: middleimportance: should knowfreq 35%

answer

  1. version once in settings
  2. build scripts omit version
  3. match requested.id.id then useVersion
  4. compare to catalog [plugins]
  5. guard with id check

basics

~10 s

In settings.gradle's pluginManagement, use resolutionStrategy.eachPlugin and call useVersion(...) when requested.id matches, so build scripts apply the plugin id without specifying a version.

solid answer

~40 s

I declare the version once in `settings.gradle` and let build scripts request the plugin by id only. Inside `pluginManagement { resolutionStrategy { eachPlugin { ... } } }`, I match on `requested.id.id` and call `useVersion("x.y.z")`. Build scripts then write `plugins { id 'com.example.foo' }` with no version, and the hook supplies it. This centralizes upgrades — one edit in settings updates every module — and prevents version drift across a multi-module build. It also lets me override a version that a third-party convention plugin would otherwise pull in. In modern Gradle I'd usually reach for a **version catalog** `[plugins]` table first for static pinning, and keep `eachPlugin` for conditional/dynamic logic (e.g. environment-based versions) the catalog cannot express.

code

kotlin · 13 lines
kotlin
// settings.gradle.kts
pluginManagement {
    val fooVersion = "1.4.0"
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "com.example.foo") {
                useVersion(fooVersion)
            }
        }
    }
}
// build.gradle.kts of any module:
plugins { id("com.example.foo") } // no version needed

go deeper

for a junior

Know that you can move the version into settings and apply by id only.

for a middle

Show the eachPlugin + useVersion pattern with an id guard and explain the multi-module benefit.

for a senior

Contrast with version catalogs and justify when the imperative hook is still warranted.

for a principal

Define an org policy: catalogs for static, convention plugins to encapsulate, eachPlugin as an escape hatch; consider caching and reproducibility impacts.

## The goal: one source of truth for plugin versions In a multi-module build, repeating `version '1.2.3'` next to every `id 'com.example.foo'` invites drift and makes upgrades painful. `eachPlugin` lets the **version live once** in settings. ## Mechanics The `plugins {}` block in a build script can request a plugin **without** a version. When Gradle resolves it, the `eachPlugin` callback fires; if you call `useVersion`, that becomes the resolved version. ``` pluginManagement { resolutionStrategy { eachPlugin { switch (requested.id.id) { case 'com.example.foo': useVersion('1.4.0'); break case 'com.example.bar': useVersion('2.1.0'); break } } } } ``` A common refinement is sourcing the version from a `gradle.properties` value or an extra property so even the settings file is data-driven. ## Reading the requested version `requested.version` is the version the build script asked for (may be null if omitted). You can override unconditionally, or only when null, or only to *raise* a floor — `eachPlugin` gives you full imperative control. ## Versus the version catalog Gradle 7+ version catalogs expose a `[plugins]` table: ``` [plugins] foo = { id = "com.example.foo", version = "1.4.0" } ``` and scripts use `alias(libs.plugins.foo)`. This is declarative, type-safe in Kotlin DSL, and the **preferred** modern approach for static pinning. Choose `eachPlugin` when you need logic — conditional versions, id-to-module remapping, or programmatic computation. ## Pitfalls - The hook runs for **every** plugin, including ones you don't intend to touch — always guard with an id check. - `useVersion` still needs a published plugin marker; if there is none, use `useModule`. - Settings-level changes invalidate plugin resolution caching, so keep the logic cheap.

  • When would you prefer a version catalog over eachPlugin for pinning?
    For static version pinning catalogs are declarative, type-safe, and shareable across builds; prefer eachPlugin only when you need conditional or computed version logic, or id-to-module remapping.
  • What happens if you call useVersion but never guard with an id check?
    The callback fires for every requested plugin, so you'd attempt to set that version on all of them — almost always a bug; always match requested.id.id first.

saying these in an interview costs you the question

  • Forgetting that the callback runs for every plugin, not just the one you care about.
  • Claiming useVersion works without a published plugin marker (it needs one; otherwise use useModule).
  • Saying you can pin in build.gradle's plugins block centrally — pinning per script is exactly what this avoids.

context