skip to content

On a large multi-module build you must decide between eachDependency rules and Gradle's more declarative mechanisms for controlling versions. How do you reason about when eachDependency is the right tool versus a smell?

level: seniorimportance: should knowfreq 20%

answer

  1. imperative callback vs declarative policy
  2. constraints/platform scale better
  3. eachDependency for conditional/computed logic
  4. per-module body has runtime cost
  5. many ad-hoc blocks = smell, lift to platform

basics

~20 s

eachDependency is an imperative per-module callback — great for conditional or computed rewrites, but harder to read and reason about at scale. For broad version policy, prefer declarative constraints or a platform; reserve eachDependency for cases needing logic.

solid answer

~50 s

`eachDependency` is the imperative escape hatch: a callback firing per module that can rewrite versions/coordinates with arbitrary logic. Its strength is **conditionality** — computed versions, branching on requested coordinates, or one-off redirects. Its weakness is that the logic is scattered, runs on every resolution, and doesn't participate as cleanly in Gradle's conflict-resolution and reporting model as declarative tools do. For a large build, the declarative options usually scale better: `constraints { }` to express preferred/required versions that flow into conflict resolution and `dependencyInsight`, and a **platform** (`java-platform` / BOM) to align a whole family of modules from one place — typically published as a convention plugin so every subproject inherits it. So the rule of thumb: use a platform/constraints for standing version policy; reach for `eachDependency` only when you genuinely need imperative logic that the declarative tools can't express. Seeing many ad-hoc `eachDependency` blocks across modules is a smell suggesting the policy belongs in a shared platform.

code

kotlin · 9 lines
kotlin
// Declarative standing policy (preferred for scale)
dependencies {
    constraints {
        implementation("com.google.guava:guava:33.2.1-jre")
    }
    implementation(platform("com.example:my-bom:1.0"))
}

// eachDependency reserved for conditional logic only

go deeper

for a junior

Know eachDependency is imperative and there are declarative alternatives for versions.

for a middle

Contrast eachDependency with constraints/platform and name a fit for each.

for a senior

Reason about scale: centralization, reporting, conflict-resolution participation, and per-module cost.

for a principal

Define org policy: standing version alignment in shared platforms/convention plugins, eachDependency only for vetted imperative exceptions.

## The spectrum of version control in Gradle Gradle offers several ways to influence resolved versions, from imperative to declarative: - **`eachDependency` (imperative)** — a per-module callback; you write code to inspect `requested` and call `useVersion`/`useTarget`. - **`constraints { }` (declarative)** — `dependencies { constraints { implementation("g:n:v") } }` states a preferred/required version without requiring the dependency to be present, and participates in conflict resolution and reporting. - **Platform / BOM (`java-platform`, `platform(...)`)** — a single module that declares aligned versions for a family; every consumer that imports it inherits them. ## Why declarative scales better At multi-module scale you want version policy that is: - **Centralized** — one place to change, not a callback duplicated across `build.gradle.kts` files. A platform published via a convention plugin gives exactly this. - **Transparent** — `dependencyInsight` clearly attributes a chosen version to a constraint or platform, with reasons. Imperative rewrites are harder to trace. - **Composable** — constraints and platforms interact predictably with conflict resolution; scattered imperative rewrites can fight each other in non-obvious order. - **Cheap** — `eachDependency` runs its body once per module per resolution; declarative metadata is evaluated by the resolver without user-code overhead per module. ## When eachDependency is the right tool It earns its place when you need **logic** the declarative tools can't express: - A version computed at resolve time from some input. - A conditional rewrite (e.g. only for a certain group, or only when the requested version matches a pattern). - A targeted, temporary redirect (`useTarget`) that you don't want to publish as policy. ```kotlin configurations.all { resolutionStrategy.eachDependency { // imperative branch the declarative tools can't express cleanly if (requested.group == "org.example" && requested.version?.startsWith("0.") == true) { useVersion("1.0.0") because("force pre-1.0 example modules onto the first stable release") } } } ``` ## The smell Many near-identical `eachDependency` blocks copy-pasted across modules to pin versions is a smell: that's *standing policy*, which belongs in a shared platform or constraints applied via a convention plugin. Consolidating gives one source of truth, better reporting, and less per-resolution overhead. ## Decision summary | Need | Prefer | |------|--------| | Standing version alignment for many modules | platform / BOM via convention plugin | | Preferred/required version, conflict-aware | `constraints { }` | | Conditional / computed / one-off rewrite | `eachDependency` | Use the most declarative tool that expresses the intent; drop to `eachDependency` only for genuine imperative needs.

  • Why does a platform/BOM scale better than eachDependency for aligning a module family?
    It centralizes the versions in one publishable place inherited by all consumers, participates cleanly in conflict resolution and insight reporting, and avoids per-module callback overhead.
  • Give a case where eachDependency is genuinely the right choice.
    A conditional or computed rewrite the declarative tools can't express — e.g. force any pre-1.0 version of a given group up to a stable release based on a version pattern.
  • What's the downside of many duplicated eachDependency blocks across modules?
    Scattered, hard-to-trace policy with per-resolution overhead; it should be consolidated into a shared platform or constraints applied via a convention plugin.

saying these in an interview costs you the question

  • Claiming eachDependency is always preferable because it's 'more powerful'.
  • Saying constraints and eachDependency are interchangeable with no trade-offs.

context