# Handling mutually exclusive dependencies

version 7.6.6-20260801035835+0000

## Introduction to component capabilities

Often a dependency graph would accidentally contain multiple implementations of the same API. This is particularly common with logging frameworks, where multiple bindings are available, and that one library chooses a binding when another transitive dependency chooses another. Because those implementations live at different GAV coordinates, the build tool has usually no way to find out that there’s a conflict between those libraries. To solve this, Gradle provides the concept of capability.

It’s illegal to find two components providing the same capability in a single dependency graph. Intuitively, it means that if Gradle finds two components that provide the same thing on classpath, it’s going to fail with an error indicating what modules are in conflict. In our example, it means that different bindings of a logging framework provide the same capability.

## Capability coordinates

A capability is defined by a `(group, module, version)` triplet. Each component defines an implicit capability corresponding to its GAV coordinates (group, artifact, version). For example, the `org.apache.commons:commons-lang3:3.8` module has an implicit capability with group `org.apache.commons`, name `commons-lang3` and version `3.8`. It is important to realize that capabilities are versioned.

## Declaring component capabilities

By default, Gradle will fail if two components in the dependency graph provide the same capability. Because most modules are currently published without Gradle Module Metadata, capabilities are not always automatically discovered by Gradle. It is however interesting to use rules to declare component capabilities in order to discover conflicts as soon as possible, during the build instead of runtime.

A typical example is whenever a component is relocated at different coordinates in a new release. For example, the ASM library lived at `asm:asm` coordinates until version `3.3.1`, then changed to `org.ow2.asm:asm` since `4.0`. It is illegal to have both ASM <= 3.3.1 and 4.0+ on the classpath, because they provide the same feature, it’s just that the component has been relocated. Because each component has an implicit capability corresponding to its GAV coordinates, we can "fix" this by having a rule that will declare that the `asm:asm` module provides the `org.ow2.asm:asm` capability:

Example 1. Conflict resolution by capability
build.gradle

    @CompileStatic
    class AsmCapability implements ComponentMetadataRule {
        void execute(ComponentMetadataContext context) {
            context.details.with {
                if (id.group == "asm" && id.name == "asm") {
                    allVariants {
                        it.withCapabilities {
                            // Declare that ASM provides the org.ow2.asm:asm capability, but with an older version
                            it.addCapability("org.ow2.asm", "asm", id.version)
                        }
                    }
                }
            }
        }
    }

build.gradle.kts

    class AsmCapability : ComponentMetadataRule {
        override
        fun execute(context: ComponentMetadataContext) = context.details.run {
            if (id.group == "asm" && id.name == "asm") {
                allVariants {
                    withCapabilities {
                        // Declare that ASM provides the org.ow2.asm:asm capability, but with an older version
                        addCapability("org.ow2.asm", "asm", id.version)
                    }
                }
            }
        }
    }

Now the build is going to fail whenever the two components are found in the same dependency graph.

| At this stage, Gradle will only make more builds fail. It will not automatically fix the problem for you, but it helps you realize that you have a problem. It is recommended to write such rules in plugins which are then applied to your builds. Then, users have to express their preferences, if possible, or fix the problem of having incompatible things on the classpath, as explained in the following section.

## Selecting between candidates

At some point, a dependency graph is going to include either incompatible modules, or modules which are mutually exclusive. For example, you may have different logger implementations and you need to choose one binding. Capabilities help realizing that you have a conflict, but Gradle also provides tools to express how to solve the conflicts.

### Selecting between different capability candidates

In the relocation example above, Gradle was able to tell you that you have two versions of the same API on classpath: an "old" module and a "relocated" one. Now we can solve the conflict by automatically choosing the component which has the highest capability version:

Example 2. Conflict resolution by capability versioning
build.gradle

    configurations.all {
        resolutionStrategy.capabilitiesResolution.withCapability('org.ow2.asm:asm') {
            selectHighestVersion()
        }
    }

build.gradle.kts

    configurations.all {
        resolutionStrategy.capabilitiesResolution.withCapability("org.ow2.asm:asm") {
            selectHighestVersion()
        }
    }

However, fixing by choosing the highest capability version conflict resolution is not always suitable. For a logging framework, for example, it doesn’t matter what version of the logging frameworks we use, we should always select Slf4j.

In this case, we can fix it by explicitly selecting slf4j as the winner:

Example 3. Substitute log4j with slf4j
build.gradle

        configurations.all {
            resolutionStrategy.capabilitiesResolution.withCapability("log4j:log4j") {
                def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'log4j-over-slf4j' }
                if (toBeSelected != null) {
                    select(toBeSelected)
                }
                because 'use slf4j in place of log4j'
            }
        }

build.gradle.kts

        configurations.all {
            resolutionStrategy.capabilitiesResolution.withCapability("log4j:log4j") {
                val toBeSelected = candidates.firstOrNull { it.id.let { id -> id is ModuleComponentIdentifier && id.module == "log4j-over-slf4j" } }
                if (toBeSelected != null) {
                    select(toBeSelected)
                }
                because("use slf4j in place of log4j")
            }
        }

Note that this approach works also well if you have multiple Slf4j bindings on the classpath: bindings are basically different logger implementations and you need only one. However, the selected implementation may depend on the configuration being resolved. For example, for tests, `slf4j-simple` may be enough but for production, `slf4-over-log4j` may be better.

| Resolution can only be made in favor of a module found in the graph. The `select` method only accepts a module found in the current candidates. If the module you want to select is not part of the conflict, you can abstain from performing a selection, effectively not resolving this conflict. It might be that another conflict exists in the graph for the same capability and will have the module you want to select. If no resolution is given for all conflicts on a given capability, the build will fail given the module chosen for resolution was not part of the graph at all. In addition `select(null)` will result in an error and so should be avoided.

For more information, check out the the capabilities resolution API.