# Capabilities

version 9.6.0-20260609080135+0000

In a dependency graph, it’s common for multiple implementations of the same API to be accidentally included, especially with libraries like logging frameworks where different bindings are selected by various transitive dependencies.

Since these implementations typically reside at different group, artifact, and version (GAV) coordinates, build tools often can’t detect the conflict.

To address this, Gradle introduces the concept of capability.

## Understanding capabilities

A capability is essentially a way to declare that different components (dependencies) offer the same functionality.

It’s illegal for Gradle to include more than one component providing the same capability in a single dependency graph. If Gradle detects two components providing the same capability (e.g., different bindings for a logging framework), it will fail the build with an error, indicating the conflicting modules. This ensures that conflicting implementations are resolved, avoiding issues on the classpath.

For instance, suppose you have dependencies on two different libraries for database connection pooling:

build.gradle.kts

    dependencies {
        implementation("com.zaxxer:HikariCP:4.0.3")  // A popular connection pool
        implementation("org.apache.commons:commons-dbcp2:2.8.0")  // Another connection pool
    }

    configurations.all {
        resolutionStrategy.capabilitiesResolution.withCapability("database:connection-pool") {
            select("com.zaxxer:HikariCP")
        }
    }

build.gradle

    dependencies {
        implementation 'com.zaxxer:HikariCP:4.0.3'  // A popular connection pool
        implementation 'org.apache.commons:commons-dbcp2:2.8.0'  // Another connection pool
    }

    configurations.all {
        resolutionStrategy.capabilitiesResolution.withCapability('database:connection-pool') {
            select('com.zaxxer:HikariCP')
        }
    }

In this case, both `HikariCP` and `commons-dbcp2` provide the same functionality (connection pooling). Gradle will fail if both are on the classpath.

Since only one should be used, Gradle’s resolution strategy allows you to select `HikariCP`, resolving the conflict.

## Understanding capability coordinates

A capability is identified by a `(group, module, version)` triplet.

Every component defines an implicit capability based on its GAV coordinates: group, artifact, and version.

For instance, the `org.apache.commons:commons-lang3:3.8` module has an implicit capability with the group `org.apache.commons`, name `commons-lang3`, and version `3.8`:

    dependencies {
        implementation("org.apache.commons:commons-lang3:3.8")
    }

It’s important to note that capabilities are versioned.

## Declaring component capabilities

To detect conflicts early, it’s useful to declare component capabilities through rules, allowing conflicts to be caught during the build instead of at runtime.

One common scenario is when a component is relocated to different coordinates in a newer release.

For example, the ASM library was published under `asm:asm` until version `3.3.1`, and then relocated to `org.ow2.asm:asm` starting with version `4.0.` Including both versions on the classpath is illegal because they provide the same feature, under different coordinates.

Since each component has an implicit capability based on its GAV coordinates, we can address this conflict by using a rule that declares the `asm:asm` module as providing the `org.ow2.asm:asm` capability:

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)
                    }
                }
            }
        }
    }

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)
                        }
                    }
                }
            }
        }
    }

With this rule in place, the build will fail if both `asm:asm` ( < = `3.3.1`) and `org.ow2.asm:asm` (`4.0+`) are present in the dependency graph.

| Gradle won’t resolve the conflict automatically, but this helps you realize that the problem exists. It’s recommended to package such rules into plugins for use in builds, allowing users to decide which version to use or to fix the classpath conflict.

## Declaring capabilities for external modules

Gradle allows you to declare capabilities not only for components you build but also for external components that don’t define them.

For example, consider the following dependencies in your build file:

build.gradle.kts

    dependencies {
        // This dependency will bring log4:log4j transitively
        implementation("org.apache.zookeeper:zookeeper:3.4.9")

        // We use log4j over slf4j
        implementation("org.slf4j:log4j-over-slf4j:1.7.10")
    }

build.gradle

    dependencies {
        // This dependency will bring log4:log4j transitively
        implementation 'org.apache.zookeeper:zookeeper:3.4.9'

        // We use log4j over slf4j
        implementation 'org.slf4j:log4j-over-slf4j:1.7.10'
    }

As it stands, it’s not obvious that this setup results in two logging frameworks on the classpath. Specifically, `zookeeper` brings in `log4j`, but we want to use `log4j-over-slf4j`.

To proactively detect this conflict, we can define a rule stating that both frameworks provide the same capability:

build.gradle.kts

    dependencies {
        // Activate the "LoggingCapability" rule
        components.all(LoggingCapability::class.java)
    }

    class LoggingCapability : ComponentMetadataRule {
        val loggingModules = setOf("log4j", "log4j-over-slf4j")

        override
        fun execute(context: ComponentMetadataContext) = context.details.run {
            if (loggingModules.contains(id.name)) {
                allVariants {
                    withCapabilities {
                        // Declare that both log4j and log4j-over-slf4j provide the same capability
                        addCapability("log4j", "log4j", id.version)
                    }
                }
            }
        }
    }

build.gradle

    dependencies {
        // Activate the "LoggingCapability" rule
        components.all(LoggingCapability)
    }

    @CompileStatic
    class LoggingCapability implements ComponentMetadataRule {
        final static Set<String> LOGGING_MODULES = ["log4j", "log4j-over-slf4j"] as Set<String>

        void execute(ComponentMetadataContext context) {
            context.details.with {
                if (LOGGING_MODULES.contains(id.name)) {
                    allVariants {
                        it.withCapabilities {
                            // Declare that both log4j and log4j-over-slf4j provide the same capability
                            it.addCapability("log4j", "log4j", id.version)
                        }
                    }
                }
            }
        }
    }

This ensures that Gradle detects the conflict and fails with a clear error message:

dependencyReport.out

    log4j:log4j:1.2.16 FAILED
       Failures:
          - Could not resolve log4j:log4j:1.2.16.
              - Module 'log4j:log4j' has been rejected:
                   Cannot select module with conflict on capability 'log4j:log4j:1.2.16' also provided by ['org.slf4j:log4j-over-slf4j:1.7.10' (compile)]

    log4j:log4j:1.2.16 FAILED
    \--- org.apache.zookeeper:zookeeper:3.4.9
         \--- compileClasspath

    org.slf4j:log4j-over-slf4j:1.7.10 FAILED
       Failures:
          - Could not resolve org.slf4j:log4j-over-slf4j:1.7.10.
              - Module 'org.slf4j:log4j-over-slf4j' has been rejected:
                   Cannot select module with conflict on capability 'log4j:log4j:1.7.10' also provided by ['log4j:log4j:1.2.16' (compile)]

    org.slf4j:log4j-over-slf4j:1.7.10 FAILED
    \--- compileClasspath

## Declaring capabilities for a local component

Every component has an implicit capability matching its GAV coordinates. However, you can also declare additional explicit capabilities, which is useful when a library published under different GAV coordinates serves as an alternate implementation of the same API:

build.gradle.kts

    configurations {
        apiElements {
            outgoing {
                capability("com.acme:my-library:1.0")
                capability("com.other:module:1.1")
            }
        }
        runtimeElements {
            outgoing {
                capability("com.acme:my-library:1.0")
                capability("com.other:module:1.1")
            }
        }
    }

build.gradle

    configurations {
        apiElements {
            outgoing {
                capability("com.acme:my-library:1.0")
                capability("com.other:module:1.1")
            }
        }
        runtimeElements {
            outgoing {
                capability("com.acme:my-library:1.0")
                capability("com.other:module:1.1")
            }
        }
    }

Capabilities must be attached to outgoing configurations, which are consumable configurations of a component.

In this example, we declare two capabilities:

1. `com.acme:my-library:1.0` - the implicit capability of the library.

2. `com.other:module:1.1` - an additional capability assigned to this library.

It’s important to declare the implicit capability explicitly because once you define any explicit capability, all capabilities must be declared, including the implicit one.

The second capability can either be specific to this library or match a capability provided by an external component. If `com.other:module` appears elsewhere in the dependency graph, the build will fail, and consumers must choose which module to use.

Capabilities are published in Gradle Module Metadata but have no equivalent in POM or Ivy metadata files. As a result, when publishing such a component, Gradle warns that this feature is only supported for Gradle consumers:

    Maven publication 'maven' contains dependencies that cannot be represented in a published pom file.
      - Declares capability com.acme:my-library:1.0
      - Declares capability com.other:module:1.1

## 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 understand the conflict, then Gradle provides you with tools 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. We can solve the conflict by automatically choosing the component which has the highest capability version:

build.gradle.kts

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

build.gradle

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

However, 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. In this case, we explicitly select `slf4j` as the preferred option:

build.gradle.kts

    configurations.configureEach {
        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")
        }
    }

build.gradle

    configurations.configureEach {
        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'
        }
    }

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 instance, in testing environments, the lightweight `slf4j-simple` logging implementation might be sufficient, while in production, a more robust solution like `logback` may be preferable.

Resolution can only be made in favor of a module that is found in the dependency graph. The `select` method accepts only a module from the current set of candidates. If the desired module is not part of the conflict, you can choose not to resolve that particular conflict, effectively leaving it unresolved. Another conflict in the graph may have the module you want to select.

If no resolution is provided for all conflicts on a given capability, the build will fail because the module chosen for resolution was not found in the graph. Additionally, calling `select(null)` will result in an error and should be avoided.

For more information, refer to the capabilities resolution API.