Two dependencies in your graph provide the same capability. What happens, and how do you resolve the conflict?
answer
- capabilitiesResolution.withCapability { select(...) }
- selectHighestVersion()
- component metadata rule adds the shared capability
- exclude or dependencySubstitution
- dependencyInsight to diagnose
basics
~10 sGradle fails resolution with a capability conflict. You resolve it with resolutionStrategy.capabilitiesResolution.withCapability("g:n") { select(...) }, or by excluding/replacing one module so only one variant provides the capability.
solid answer
~40 sWhen two variants in the resolved graph expose the **same capability** (and they're not just two versions of the same module that Gradle auto-resolves by version), Gradle stops with a **capability conflict** error — it refuses to put both on the classpath because they'd provide the same thing twice. You resolve it deterministically: ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("com.google.guava:guava") { select("com.google.guava:guava:0") } } ``` `select(...)` picks the winning candidate (you can also `selectHighestVersion()` or pick by reason). Alternatives: declare a **component metadata rule** that maps both onto the same capability so the conflict is *detected*, then exclude the loser, or substitute one module for the other with `resolutionStrategy.dependencySubstitution`. The key idea: capability conflicts are a feature — they surface 'same thing, different coordinates' clashes early instead of letting duplicate classes blow up at runtime.
code
kotlin · 9 linesconfigurations.all {
resolutionStrategy.capabilitiesResolution {
withCapability("com.google.guava:guava") {
// pick a specific candidate
select("com.google.guava:guava:0")
because("guava supersedes google-collections")
}
}
}go deeper
Know that two modules providing the same capability cause a build failure you must resolve.
Name capabilitiesResolution.select and the exclude alternative.
Walk through detecting via metadata rules, resolving with select/selectHighestVersion/substitution, and diagnosing with dependencyInsight.
Establish org-wide capability rules (e.g. a convention plugin that de-dupes known clashing pairs) so every build resolves them consistently.
## Why a conflict happens Gradle resolves a dependency graph into a set of **variants**. A rule of resolution: **no two selected variants may provide the same capability**. There are two flavours: 1. **Same module, different versions** — both provide the implicit GAV capability `g:n`. Gradle resolves this *automatically* by version conflict resolution (highest wins by default). No error. 2. **Different modules, same explicit capability** — e.g. `guava` and `google-collections` both declared to provide `com.google.guava:guava`. Gradle **cannot** auto-pick (different versions, different artifacts) so it raises a **capability conflict** and fails the build. ## Detecting the conflict For third-party modules that don't declare shared capabilities, you add a **component metadata rule**: ```kotlin dependencies { components.withModule("com.google.collections:google-collections") { allVariants { withCapabilities { addCapability("com.google.guava", "guava", "1.0") } } } } ``` Now both provide `com.google.guava:guava` → Gradle detects the clash. ## Resolving the conflict ### 1. capabilitiesResolution.select ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("com.google.guava:guava") { select("com.google.guava:guava:0") // pick guava, drop google-collections } } ``` You can also call `selectHighestVersion()` inside the block, or inspect `getCandidates()` and choose programmatically with a `because("...")` reason. ### 2. Exclude the loser ```kotlin dependencies { implementation("some:lib:1.0") { exclude(group = "com.google.collections") } } ``` ### 3. Dependency substitution ```kotlin configurations.all { resolutionStrategy.dependencySubstitution { substitute(module("com.google.collections:google-collections")) .using(module("com.google.guava:guava:31.1-jre")) .because("google-collections is superseded by guava") } } ``` ## Feature-variant conflicts The same machinery resolves conflicts between **feature variants**: if two modules expose the same feature capability (e.g. via shading), you `select` the intended one. This is exactly how Gradle prevents two implementations of one capability — like two test-fixtures or two 'logging-backend' features — landing together. ## Diagnosing `./gradlew dependencyInsight --configuration runtimeClasspath --dependency guava` and the build scan show which capability clashed and why a candidate was chosen.
- Why doesn't 'two versions of the same module' raise a capability conflict error?Both expose the same implicit GAV capability, but Gradle has a built-in rule (highest version wins) to auto-resolve same-module version conflicts, so it picks one silently instead of failing.
- What's the difference between select() and selectHighestVersion() in capabilitiesResolution?select(candidate) picks a specific named candidate; selectHighestVersion() asks Gradle to pick the candidate with the highest version among those providing the capability.
- How would you make Gradle even detect that guava and google-collections clash?Add a component metadata rule (components.withModule { withCapabilities { addCapability(...) } }) so both advertise the same capability; only then is there a conflict to resolve.
saying these in an interview costs you the question
- Claiming Gradle silently keeps both modules — it fails on an explicit capability conflict.
- Confusing version conflict resolution (auto) with capability conflict resolution (requires a rule).