Your Gradle build fails with 'Cannot select module ... Multiple variants ... provide the same capability'. What does this mean and what are your options?
answer
- same capability => mutually exclusive
- dependencyInsight to find culprits
- capabilitiesResolution.select / selectHighestVersion
- exclude transitive loser
- substitute to remove a provider
basics
~20 sTwo variants in your graph declare the same capability, so they're mutually exclusive and Gradle refuses to put both on the classpath. You must pick one — via a capabilitiesResolution rule, or by removing/substituting one of the modules.
solid answer
~40 sThe error means **two variants in the resolution claim the same capability** (e.g. two logging implementations), so Gradle treats them as mutually exclusive and won't silently keep both. Your options: 1. **Resolve the capability** — `resolutionStrategy.capabilitiesResolution.withCapability("g:n") { select(...) }` to choose the winner programmatically (the recommended, explicit fix). 2. **Remove the loser** — drop one dependency or `exclude` the transitive that drags it in. 3. **Substitute** — replace one module with the other so only one capability provider remains. Which you choose depends on whether the conflict is your direct dependency (just drop one) or comes transitively (resolve or substitute). Reach for `dependencyInsight` to see *who* pulls each provider in. The key insight: this is Gradle protecting you from a duplicate-feature classpath, not a bug to silence.
code
bash · 4 lines# find which dependencies bring each conflicting provider
./gradlew dependencyInsight \
--configuration runtimeClasspath \
--dependency log4j-over-slf4jgo deeper
Recognize the message means two modules are the same feature and you must pick one.
List the options (resolution rule, exclude, substitute) and use dependencyInsight to diagnose.
Choose the right fix based on direct vs transitive origin and centralize the policy.
Standardize how teams resolve such conflicts (shared rules) so each isn't reinvented per build.
## Reading the error The message names a capability coordinate and lists the variants/modules that provide it. It appears because, during resolution, more than one variant declared the **same capability**, and capabilities are required to be **unique** in a resolved graph. Gradle therefore fails rather than producing a classpath with two implementations of one feature. This happens either because: - Two modules *natively* declare the same capability (rare in the wild), or - A component metadata rule you (or a plugin) wrote added a shared capability to make them mutually exclusive. ## Diagnose first Run dependency insight to see who brings each provider: ```bash ./gradlew dependencyInsight --configuration runtimeClasspath --dependency <module> ``` This shows the requesting paths and the conflicting capability, so you know whether the offender is a direct or transitive dependency. ## Your options ### 1. Resolve the capability (preferred for transitive conflicts) ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("org.slf4j:slf4j-logging") { selectHighestVersion() // or select(specificCandidate) } } ``` Explicit, centralized, survives transitive changes. ### 2. Remove the loser If one provider is a *direct* dependency you don't need, delete it. If it's transitive, exclude it: ```kotlin dependencies { implementation("com.example:lib") { exclude(group = "log4j", module = "log4j") } } ``` ### 3. Substitute one for the other When the right fix is "always use B instead of A," a substitution removes one capability provider entirely (a sibling mechanism), leaving no conflict. ## What NOT to do - Don't blindly add both with an exclude scattered everywhere — it's fragile. Centralize with a capability resolution rule. - Don't downgrade the failure to a warning by ignoring it; the duplicate classes will bite at runtime. ## Mental model A capability conflict is a *forced decision point*. Gradle has determined that two things on your graph are the same feature and only one may remain. The fix is always "which one wins," expressed either by selecting it (capabilitiesResolution) or by ensuring only one ever enters the graph (exclude/substitute).
- How do you find which dependency drags in the conflicting provider?Run ./gradlew dependencyInsight --configuration <config> --dependency <module> to see the requesting paths and the capability in conflict.
- When would you exclude rather than write a capabilitiesResolution rule?When the loser arrives transitively from a single dependency and you simply never want it; for graph-wide or repeated conflicts a centralized resolution rule is cleaner.
saying these in an interview costs you the question
- Treating it as a version conflict and calling force() — wrong mechanism.
- Suppressing the failure and shipping both providers — leads to runtime LinkageError/duplicate classes.
- Excluding the wrong (winning) provider and breaking the feature you actually wanted.