skip to content

Where and how do you declare a module replacement rule, and how can you apply it consistently across many subprojects?

level: middleimportance: should knowfreq 25%

answer

  1. dependencies { modules { module(...) { replacedBy(...) } } }
  2. lives in dependencies.modules, NOT resolutionStrategy
  3. project-scoped rule
  4. convention plugin / buildSrc for cross-project reuse
  5. avoid allprojects for coupling/perf

basics

~20 s

Declare it in the dependencies block: dependencies { modules { module('g:old') { replacedBy('g:new', 'reason') } } }. To apply across subprojects, put it in a convention plugin or a shared script applied to each project.

solid answer

~40 s

Module replacement is declared inside a project's `dependencies` block under a `modules { }` container: `dependencies { modules { module("g:old") { replacedBy("g:new", "reason") } } }`. It is project-scoped — it affects that project's resolvable configurations. To make it consistent across a multi-project build, you don't repeat it in every `build.gradle.kts`; instead you put it in a **convention/precompiled plugin** (e.g. `buildSrc` or an included build) that each subproject applies, or apply it via a shared script. Centralising it ensures all modules handle the renamed coordinate identically and the rule lives in one auditable place. Avoid `allprojects { }` blocks in modern setups (discouraged for cross-config coupling); a convention plugin is the idiomatic mechanism.

code

kotlin · 11 lines
kotlin
// buildSrc/src/main/kotlin/my.dependency-rules.gradle.kts
dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava", "merged into guava")
        }
    }
}

// each subproject build.gradle.kts
plugins { id("my.dependency-rules") }

go deeper

for a junior

Recall the dependencies { modules { ... } } placement and the basic syntax.

for a middle

Note it is project-scoped, not under resolutionStrategy, and that convention plugins share it across subprojects.

for a senior

Explain why allprojects is discouraged and how a precompiled plugin composes with other dependency conventions.

for a principal

Define the org standard: a shared dependency-rules plugin owning coordinate renames, versioned and reviewed centrally.

## Declaration site Module replacement lives in the **`dependencies`** block, inside a nested `modules` container: ```kotlin dependencies { modules { module("com.google.collections:google-collections") { replacedBy("com.google.guava:guava", "merged into guava") } } } ``` It is **per-project**: the rule influences resolution for the configurations of the project where it is declared. It is *not* part of `resolutionStrategy` (that's where substitution/force/eachDependency live) — replacement has its own `dependencies.modules` home. ## Applying it across a multi-project build In a build with many subprojects, you want every subproject that could see the legacy coordinate to apply the same rule. The idiomatic way is a **convention plugin**: ```kotlin // buildSrc/src/main/kotlin/my.dependency-rules.gradle.kts dependencies { modules { module("com.google.collections:google-collections") { replacedBy("com.google.guava:guava", "merged into guava") } } } ``` Then each subproject: ```kotlin plugins { id("my.dependency-rules") } ``` Benefits: single source of truth, testable, no copy-paste drift, and it composes with other dependency-management conventions (platforms, locking). ## Alternatives and what to avoid - `allprojects { dependencies { modules { ... } } }` in the root build works but is discouraged in modern Gradle because cross-project configuration couples projects and hurts configuration-time performance/isolation. Prefer convention plugins. - A standalone script applied via `apply(from = ...)` is acceptable for small builds but lacks the type-safety and reuse of precompiled plugins. ## Verifying it is applied everywhere Run `./gradlew :someSubproject:dependencyInsight --dependency google-collections` per subproject (or a custom task) to confirm the eviction with your reason text shows up consistently.

  • Is module replacement declared under resolutionStrategy?
    No. Substitution, force, and eachDependency live under resolutionStrategy, but module replacement has its own home in the dependencies.modules block.
  • Why prefer a convention plugin over allprojects {} for sharing the rule?
    Convention plugins give a single, testable source of truth and avoid cross-project configuration coupling, which Gradle discourages because it hurts configuration-time isolation and performance.

saying these in an interview costs you the question

  • Placing replacedBy inside resolutionStrategy — wrong DSL location.
  • Copy-pasting the rule into every subproject instead of centralising it.
  • Putting a version into the coordinates.

context