skip to content

How does `resolutionStrategy.failOnVersionConflict()` interact with `force` and strict constraints, and when would you enable it?

level: seniorimportance: nice to knowfreq 24%

answer

  1. fail instead of highest-wins
  2. force/strictly satisfy the conflict
  3. enable → pin each conflict deliberately
  4. centralise pins in a platform/BOM
  5. pairs with dependency locking

basics

~20 s

failOnVersionConflict() makes Gradle fail whenever two versions of a module are requested, instead of silently picking the highest. force resolves such conflicts and avoids the failure; you enable it to surface every version disagreement and pin them deliberately.

solid answer

~40 s

`resolutionStrategy { failOnVersionConflict() }` flips conflict resolution from 'highest wins' to 'fail if there's any disagreement'. It's a discipline switch: instead of letting Gradle silently choose, the build breaks and forces you to make every version conflict explicit. `force` (and a `strictly` constraint) *resolve* a conflict authoritatively, so a forced/strict module no longer trips `failOnVersionConflict` — the pin is the explicit decision. The intended workflow: enable `failOnVersionConflict()`, run the build, and for each reported conflict add a deliberate `force`/`strictly` (ideally in one place, often a platform) with a `because(...)`. The downside is that any new transitive bump breaks the build until acknowledged — high control, more maintenance. Teams often pair it with dependency locking. Enable it in shared/critical modules where unintended version drift is dangerous.

code

kotlin · 5 lines
kotlin
configurations.all {
  resolutionStrategy {
    failOnVersionConflict()
  }
}

go deeper

for a junior

Know it changes 'highest wins' into a hard failure on any version disagreement.

for a middle

Explain that force/strictly resolve the conflict so it no longer fails.

for a senior

Describe the enable-then-pin workflow and centralising decisions in a platform.

for a principal

Weigh control vs maintenance org-wide; combine with locking and CI gating as policy.

## Default vs strict conflict handling By default Gradle silently applies **highest-version-wins** when a module is requested at multiple versions. That's convenient but means the resolved graph can shift unnoticed as transitives evolve. ```kotlin configurations.all { resolutionStrategy { failOnVersionConflict() } } ``` With `failOnVersionConflict()`, *any* unresolved version disagreement aborts resolution with a report of the conflicting modules and the versions requested. Nothing is chosen for you. ## How force / strictly satisfy it A conflict is 'resolved' (and therefore not a failure) when an authoritative decision exists: - A **`force`** on that module fixes the version. - A **`strictly`** constraint fixes the version (and additionally fails if *that* requirement is itself violated). So the practical loop is: turn on `failOnVersionConflict`, build, read each conflict, and add an intentional pin. Centralising those pins in a `java-platform`/BOM keeps them in one governed place: ```kotlin // platform/build.gradle.kts dependencies { constraints { api("org.slf4j:slf4j-api") { version { strictly("2.0.9") } } api("com.google.guava:guava") { version { strictly("32.1.3-jre") } } } } ``` ## Trade-offs | Benefit | Cost | |---|---| | No silent version drift | Every new transitive bump breaks the build | | Forces deliberate, documented pins | More maintenance churn | | Reproducible, auditable graph | Needs discipline / a platform to scale | ## Related controls - `failOnDynamicVersions()` / `failOnChangingVersions()` / `failOnNonReproducibleResolution()` — companion strictness switches. - **Dependency locking** (`dependencyLocking { lockAllConfigurations() }`) is often combined so the chosen versions are pinned in a lockfile and CI verifies them. ## When to enable Use it for libraries and high-stakes services where an accidental transitive upgrade could introduce a regression or CVE, and where you already maintain a platform to hold the pins. For quick app prototypes the maintenance cost usually isn't worth it.

  • After enabling failOnVersionConflict, how do you stop a specific conflict from failing the build?
    Make a deliberate decision for that module: add a force or, preferably, a strictly constraint (ideally in a platform) with a because() explaining the choice.
  • What's a common companion to failOnVersionConflict for reproducibility?
    Dependency locking (lockAllConfigurations) plus failOnDynamicVersions/failOnChangingVersions, so the resolved graph is pinned in lockfiles and verified in CI.

saying these in an interview costs you the question

  • Thinking failOnVersionConflict pins versions itself — it only fails; you still must decide.
  • Believing force is ignored under failOnVersionConflict (it actually resolves the conflict).
  • Enabling it everywhere without a platform to hold the pins, then drowning in churn.

context