skip to content

How do you resolve a capability conflict programmatically in Gradle, picking one variant over another?

level: seniorimportance: must knowfreq 40%

answer

  1. resolutionStrategy.capabilitiesResolution.withCapability
  2. candidates list + select(candidate)
  3. selectHighestVersion() shortcut
  4. because(reason) for insight
  5. no rule => hard failure

basics

~10 s

Use resolutionStrategy.capabilitiesResolution.withCapability('group:name') { select(...) } inside a configuration. The closure receives the conflicting candidates and you call select() to pick one, or selectHighestVersion() to take the newest.

solid answer

~40 s

You hook into `resolutionStrategy.capabilitiesResolution`. For a given capability you register a rule: ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("org.slf4j:slf4j-logging") { val pick = candidates.firstOrNull { it.id.let { id -> id is ModuleComponentIdentifier && id.module == "slf4j-jdk14" } } if (pick != null) select(pick) else selectHighestVersion() } } ``` The rule's `candidates` list contains every variant claiming that capability. You call **`select(candidate)`** to choose the winner, optionally with a `because("reason")`, or **`selectHighestVersion()`** as a shortcut. Candidates not selected are dropped from the graph. If you register no rule, the conflict is a hard build failure. The rule runs at resolution time per configuration, so `configurations.all` (or a convention plugin) applies it everywhere consistently.

code

kotlin · 14 lines
kotlin
configurations.all {
    resolutionStrategy.capabilitiesResolution.withCapability("log4j:log4j") {
        // prefer the slf4j bridge over real log4j
        val bridge = candidates.firstOrNull {
            (it.id as? ModuleComponentIdentifier)?.module == "log4j-over-slf4j"
        }
        if (bridge != null) {
            select(bridge)
            because("route legacy log4j calls through slf4j")
        } else {
            selectHighestVersion()
        }
    }
}

go deeper

for a junior

Know that there's a withCapability rule where you select() the winner; details optional.

for a middle

Write a basic rule using selectHighestVersion() and explain candidates/select.

for a senior

Inspect candidate ids to select a specific module, add because(), and place the rule in configurations.all.

for a principal

Centralize capability resolution in convention plugins as org policy and reason about cross-module version comparability.

## The resolution hook Capability conflicts are resolved through `ResolutionStrategy.getCapabilitiesResolution()`, reached via `configurations.<name>.resolutionStrategy.capabilitiesResolution`. You register a rule for a specific capability coordinate: ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") { // 'this' is CapabilityResolutionDetails selectHighestVersion() } } ``` Note the capability you pass to `withCapability` is the **shared capability coordinate** (group:name, version optional), not necessarily a module GAV — it is whatever capability the conflicting variants declare. ## Inside the rule: CapabilityResolutionDetails The rule receives a `CapabilityResolutionDetails` with: - **`getCandidates()`** — the list of `CapabilityResolutionDetails.Candidate`/component candidates competing for this capability. Each exposes its `getId()` (a `ComponentIdentifier`, usually a `ModuleComponentIdentifier` with group/module/version) and variant name. - **`select(candidate)`** — choose this candidate as the winner; the others are evicted. - **`selectHighestVersion()`** — convenience: pick the candidate with the highest version. - **`because(reason)`** — attach a human-readable reason that shows up in `dependencyInsight`/build scans. ## A realistic pick ```kotlin configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("org.slf4j:slf4j-logging") { candidates.find { (it.id as? ModuleComponentIdentifier)?.module == "slf4j-simple" }?.let { select(it) } because("standardise on slf4j-simple for logging binding") } } ``` If your `find` returns null (the preferred module isn't a candidate here) you can fall back to `selectHighestVersion()` so you never leave the conflict unresolved. ## Where to put it - `configurations.all { ... }` applies to every configuration in the project. - Better for multi-project builds: put it in a **convention/precompiled plugin** applied to all subprojects, so the policy is centralized. ## What if you don't resolve it The default is a **hard failure** with a message listing the conflicting modules and the shared capability. Gradle will not silently pick one — that's the whole point: mutually-exclusive features must be a deliberate decision. ## Relationship to declaring the capability Resolution only fires once a conflict *exists*. For third-party modules that don't already declare a shared capability, you must first **declare** it via a component metadata rule (`withCapabilities { addCapability(...) }`). Resolution and declaration are two halves of the same workflow.

  • What does the rule's candidates list contain?
    Every variant/component claiming the conflicting capability, each with an id (ComponentIdentifier) you can inspect to decide which to select().
  • Difference between select() and selectHighestVersion()?
    select() picks a specific candidate you choose explicitly; selectHighestVersion() is a convenience that auto-picks the highest-versioned candidate.
  • Why apply it with configurations.all or a convention plugin?
    So the resolution policy is consistent across every configuration and every subproject, rather than per-configuration and easy to forget.

saying these in an interview costs you the question

  • Calling resolutionStrategy.force(...) — that's version forcing, a sibling concern, not capability resolution.
  • Thinking selectHighestVersion makes semantic sense across two *different* modules — versions aren't comparable across unrelated modules, so usually you select() explicitly.
  • Assuming the conflict auto-resolves to 'highest' like a version conflict — it fails hard until you write a rule.

context