What is a 'capability' in Gradle dependency management, and what problem does it solve compared to plain version conflict resolution?
answer
- group:name:version identity of a variant
- implicit capability = the module's own GAV
- two different modules, same feature
- guava vs google-collections, slf4j shims
- conflict = hard failure by default
basics
~20 sA capability is a label (group:name:version) saying 'I provide this feature'. By default a module provides one capability matching its GAV. Two modules offering the same capability conflict, even if they are different modules — like log4j and log4j-over-slf4j both providing logging.
solid answer
~40 sA **capability** is an identity Gradle attaches to a variant, of the form `group:name:version`, that declares *what feature* it provides — independent of the module's own coordinates. Every component implicitly declares one capability equal to its GAV. Plain conflict resolution only deduplicates **different versions of the same module**; it cannot detect that two *different* modules implement the *same* thing (e.g. `com.google.guava:guava` vs `com.google.collections:google-collections`, or `slf4j` vs `log4j-over-slf4j`). When you declare that both modules provide the same capability, Gradle detects them as **mutually exclusive** and fails the build with a capability conflict, forcing you to choose one. This turns a silent classpath clash (duplicate classes, `LinkageError` at runtime) into an early, explicit resolution decision.
code
kotlin · 10 linesdependencies {
// teach Gradle that these two distinct modules are the same feature
components.withModule("com.google.collections:google-collections") {
allVariants {
withCapabilities {
addCapability("com.google.guava", "guava", "1.0")
}
}
}
}go deeper
Know that a capability is a label for 'what a module provides' and that two modules claiming the same capability conflict.
Explain implicit GAV capability, give a real example (logging shims, guava), and contrast with plain version conflict resolution.
Discuss adding capabilities via component metadata rules and why conflicts are failures by default.
Frame capabilities as a governance tool to enforce one canonical implementation org-wide, codified in convention plugins.
## What a capability is In Gradle's dependency model, every resolved thing is a **variant** of a **component**. A **capability** is a coordinate of the form `group:name:version` that a variant declares to say "I provide *this* feature." It is orthogonal to the component's own GAV (group:artifact:version). By default, every component declares exactly **one implicit capability** equal to its own GAV. So `org.apache.commons:commons-lang3:3.12.0` provides the capability `org.apache.commons:commons-lang3:3.12.0`. ## The problem it solves Default conflict resolution answers the question *"the graph wants two versions of the same module — which version wins?"* (highest version by default). But it is blind to a different problem: **two distinct modules that are really the same feature**. Classic examples: - `com.google.collections:google-collections` and `com.google.guava:guava` (Guava absorbed google-collections). - `org.slf4j:log4j-over-slf4j` and `log4j:log4j` (one is a drop-in replacement for the other). - `javax.*` vs `jakarta.*` of the same API. Without capabilities, both end up on the classpath. You get duplicate classes and, at runtime, `NoSuchMethodError`/`LinkageError`/non-deterministic class loading. ## How capabilities fix it If you make both modules declare the **same capability** (e.g. both declare `org.apache.logging:logging:1.0`), Gradle sees two variants offering one capability in the same resolution and raises a **capability conflict** — by default a hard failure. You then resolve it explicitly, choosing a winner. There are two places this happens: 1. **Component metadata rules** — you *add* a capability to third-party modules that don't declare it (e.g. tell Gradle that both logging shims provide the same capability). 2. **Resolution strategy** — `configurations.all { resolutionStrategy.capabilitiesResolution.withCapability(...) { selectHighestVersion() / select(...) } }` to pick the winner. ## Why it matters It converts a **silent runtime hazard** into a **build-time, explicit decision**. The cost is that conflicts are failures by default, so you must wire up resolution. ```kotlin dependencies { components.all { // declares both modules provide one shared capability if (id.group == "log4j" && id.name == "log4j") { allVariants { withCapabilities { addCapability("org.slf4j", "slf4j-logging", "1.0") } } } } } ```
- What capability does a component declare if you do nothing?Exactly one implicit capability equal to its own GAV (group:artifact:version).
- Why is a capability conflict a failure by default rather than 'pick highest'?Because two modules with the same capability are usually mutually exclusive (duplicate classes); silently picking one could break the build, so Gradle forces an explicit choice.
Version conflict resolution is choosing between two editions of the same book. A capability conflict is realising two differently-titled books are actually the same book — and you can only keep one on the shelf.
saying these in an interview costs you the question
- Saying capabilities are just another name for the module version — they are an independent feature identity.
- Claiming Gradle auto-detects guava vs google-collections out of the box — it does not; you must declare the shared capability.