Understanding Variant Selection
Gradle’s dependency management engine is variant aware.
In the previous section, Gradle built a graph of resolved dependencies. During graph resolution, Gradle must select the proper variants of each dependency based on the requirements of your build.

Variants represent different ways a component can be used, such as for Java compilation, native linking, or documentation. Each variant may have its own artifacts and dependencies.
Gradle uses attributes to determine which variant to choose. These attributes add context to each variant, describing when they should be used.
Components
A component, in most cases, represents a version of a module.
A component:
-
contains variants
-
which contain one or more artifacts
-
which contain zero or more dependencies
-
which is described by metadata
-

In the example above, org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
is the component.
It has a module org.jetbrains.kotlinx:kotlinx-serialization-json
and a version 1.5.1
:

Variant attributes
Variants represent different versions or aspects of a component, like api
vs implementation
or jar
vs classes
.
In the example above, org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
has four variants: jvm
, android
, js
, and native
.
Attributes are type-safe key-value pairs used by both the consumer and the producer during variant selection: attribute : value
.
In the example above, there are two attributes of importance to note for the variants of org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
:
Variant | Attribute 1: | Attribute 2: |
---|---|---|
JVM |
|
|
Android |
|
|
Javascript |
|
|
Native |
|
|
However, it is also the case that in the metadata of org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
, there are many more attributes that describe each variant such as org.gradle.libraryelements: jar
or org.gradle.category: library
:
"variants": [
...
{
"name": "jsLegacyRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.usage": "kotlin-runtime",
"org.jetbrains.kotlin.js.compiler": "legacy",
"org.jetbrains.kotlin.platform.type": "js"
}
},
{
"name": "jvmRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime",
"org.jetbrains.kotlin.platform.type": "jvm"
}
},
...
]
You can learn more about variants and attributes in Variants and Attributes.
There are two types of attributes Gradle uses to match the available variants to the one required for the build:
-
Consumer attributes: Define the desired characteristics of a variant requested by a resolvable configuration.
-
Producer attributes: Each variant has a set of attributes that describe its purpose.
Variant attribute matching
There are no restrictions on how many variants a component can define. A typical component will include at least an implementation variant but may also provide additional variants, such as test fixtures, documentation, or source code. Furthermore, a component can offer different variants for the same usage, depending on the consumer. For instance, during compilation, a component may provide different headers for Linux, Windows, and macOS.
Gradle performs variant-aware selection by matching the attributes specified by the consumer with those defined by the producer. The details of this process are covered in the section below.

The variant name is primarily used for debugging and error messages. It does not play a role in variant matching; only the variant’s attributes are used in the matching process. |
Variant attribute matching algorithm
Gradle’s dependency resolution engine performs the following variant matching algorithm to find the best result (or fail):
-
Each candidate’s attribute value is compared to the consumer’s requested attribute value. A candidate is considered compatible if its value matches the consumer’s value exactly, passes the attribute’s compatibility rule, or is not provided.
-
If only one candidate is considered compatible, that candidate wins.
-
If several candidates are compatible, but one of the candidates matches all of the same attributes as the other candidates, Gradle chooses that candidate. This is the candidate with the "longest" match.
-
If several candidates are compatible and are compatible with an equal number of attributes, Gradle needs to disambiguate the candidates.
-
For each requested attribute, if a candidate does not have a value matching the disambiguation rule, it’s eliminated from consideration.
-
If the attribute has a known precedence, Gradle will stop as soon as there is a single candidate remaining.
-
If the attribute does not have a known precedence, Gradle must consider all attributes.
-
-
If several candidates still remain, Gradle will start to consider "extra" attributes to disambiguate between multiple candidates. Extra attributes are attributes that were not requested by the consumer but are present on at least one candidate. These extra attributes are considered in precedence order.
-
If the attribute has a known precedence, Gradle will stop as soon as there is a single candidate remaining.
-
After all extra attributes with precedence are considered, the remaining candidates can be chosen if they are compatible with all of the non-ordered disambiguation rules.
-
-
If several candidates still remain, Gradle will consider extra attributes again. A candidate can be chosen if it has the fewest number of extra attributes.
If at any step no compatible candidates remain, resolution fails. Additionally, Gradle outputs a list of all compatible candidates from step 1 to help with debugging variant matching failures.
Plugins and ecosystems can influence the selection algorithm by implementing compatibility rules, disambiguation rules, and defining the precedence of attributes. Attributes with a higher precedence are used to eliminate candidates in order.
For example, in the Java ecosystem, the org.gradle.usage
attribute has a higher precedence than org.gradle.libraryelements
.
This means that if two candidates were available with compatible values for both org.gradle.usage
and org.gradle.libraryelements
, Gradle will choose the candidate that passes the disambiguation rule for org.gradle.usage
.
There are two exceptions to the variant-aware resolution process:
|
A simple example
Let’s walk through an example where a consumer is trying to use a library for compilation.
First, the consumer details how it’s going to use the result of dependency resolution. This is achieved by setting attributes on the consumer’s resolvable configuration.
In this case, the consumer wants to resolve a variant that matches org.gradle.usage=java-api
.
Next, the producer exposes different variants of its component:
-
API variant (named
apiElements
) with the attributeorg.gradle.usage=java-api
-
Runtime variant (named
runtimeElements
) with the attributeorg.gradle.usage=java-runtime
Finally, Gradle evaluates the variants and selects the correct one:
-
The consumer requests a variant with attributes
org.gradle.usage=java-api
-
The producer’s
apiElements
variant matches this request. -
The producer’s
runtimeElements
variant does not match.
As a result, Gradle selects the apiElements
variant and provides its artifacts and dependencies to the consumer.
A complicated example
In real-world scenarios, both consumers and producers often work with multiple attributes.
For instance, a Java Library project in Gradle will involve several attributes:
-
org.gradle.usage
describes how the variant is used. -
org.gradle.dependency.bundling
describes how the variant handles dependencies (e.g., shadow jar, fat jar, regular jar). -
org.gradle.libraryelements
describes the packaging of the variant (e.g., classes or jar). -
org.gradle.jvm.version
describes the minimal version of Java the variant targets. -
org.gradle.jvm.environment
describes the type of JVM the variant targets.
Let’s consider a scenario where the consumer wants to run tests using a library on Java 8, and the producer supports two versions: Java 8 and Java 11.
Step 1: Consumer specifies the requirements.
The consumer wants to resolve a variant that:
-
Can be used at runtime (
org.gradle.usage=java-runtime
). -
Can run on at least Java 8 (
org.gradle.jvm.version=8
).
Step 2: Producer exposes multiple variants.
The producer offers variants for both Java 8 and Java 11 for both API and runtime usage:
-
API variant for Java 8 (named
apiJava8Elements
) with attributesorg.gradle.usage=java-api
andorg.gradle.jvm.version=8
. -
Runtime variant for Java 8 (named
runtime8Elements
) with attributesorg.gradle.usage=java-runtime
andorg.gradle.jvm.version=8
. -
API variant for Java 11 (named
apiJava11Elements
) with attributesorg.gradle.usage=java-api
andorg.gradle.jvm.version=11
. -
Runtime variant for Java 11 (named
runtime11Elements
) with attributesorg.gradle.usage=java-runtime
andorg.gradle.jvm.version=11
.
Step 3: Gradle matches the attributes.
Gradle compares the consumer’s requested attributes with the producer’s variants:
-
The consumer requests a variant with
org.gradle.usage=java-runtime
andorg.gradle.jvm.version=8
. -
Both
runtime8Elements
andruntime11Elements
match theorg.gradle.usage=java-runtime
attribute. -
The API variants (
apiJava8Elements
andapiJava11Elements
) are discarded as they don’t matchorg.gradle.usage=java-runtime
. -
The variant
runtime8Elements
is selected because it is compatible with Java 8. -
The variant
runtime11Elements
is incompatible because it requires Java 11.
Gradle selects runtime8Elements
and provides its artifacts and dependencies to the consumer.
What happens if the consumer sets org.gradle.jvm.version=7
?
In this case, dependency resolution would fail, with an error explaining there is no suitable variant. Gradle knows the consumer requires a Java 7-compatible library, but the producer’s minimum version is 8.
If the consumer requested org.gradle.jvm.version=15
, Gradle could choose either the Java 8 or Java 11 variant. Gradle would then select the highest compatible version—Java 11.
Variant selection errors
When Gradle attempts to select the most compatible variant of a component, resolution may fail due to:
-
Ambiguity error: When more than one variant from the producer matches the consumer’s attributes, leading to confusion over which to select.
-
Incompatibility error: When none of the producer’s variants match the consumer’s attributes, causing the resolution to fail.
Dealing with ambiguity errors
An ambiguous variant selection looks like this:
> Could not resolve all files for configuration ':compileClasspath'.
> Could not resolve project :lib.
Required by:
project :ui
> Cannot choose between the following variants of project :lib:
- feature1ApiElements
- feature2ApiElements
All of them match the consumer attributes:
- Variant 'feature1ApiElements' capability org.test:test-capability:1.0:
- Unmatched attribute:
- Found org.gradle.category 'library' but wasn't required.
- Compatible attributes:
- Provides org.gradle.dependency.bundling 'external'
- Provides org.gradle.jvm.version '11'
- Required org.gradle.libraryelements 'classes' and found value 'jar'.
- Provides org.gradle.usage 'java-api'
- Variant 'feature2ApiElements' capability org.test:test-capability:1.0:
- Unmatched attribute:
- Found org.gradle.category 'library' but wasn't required.
- Compatible attributes:
- Provides org.gradle.dependency.bundling 'external'
- Provides org.gradle.jvm.version '11'
- Required org.gradle.libraryelements 'classes' and found value 'jar'.
- Provides org.gradle.usage 'java-api'
In this scenario, all compatible candidate variants are listed along with their attributes:
-
Unmatched attributes: Shown first, these indicate what attributes may be missing or misaligned for selecting the proper variant.
-
Compatible attributes: Shown next, these highlight how the candidate variants align with the consumer’s requirements.
-
Incompatible attributes: Will not be shown, as incompatible variants are excluded.
In the example above, the issue isn’t with attribute matching but with capability matching.
Both feature1ApiElements
and feature2ApiElements
offer the same attributes and capabilities, making them indistinguishable to Gradle.
To resolve this, you can modify the producer (project :lib
) to provide different capabilities or express a capability choice on the consumer side (project :ui
) to disambiguate between the variants.
Dealing with no matching variant errors
A no matching variant error might look like this:
> No variants of project :lib match the consumer attributes:
- Configuration ':lib:compile':
- Incompatible attribute:
- Required artifactType 'dll' and found incompatible value 'jar'.
- Other compatible attribute:
- Provides usage 'api'
- Configuration ':lib:compile' variant debug:
- Incompatible attribute:
- Required artifactType 'dll' and found incompatible value 'jar'.
- Other compatible attributes:
- Found buildType 'debug' but wasn't required.
- Provides usage 'api'
- Configuration ':lib:compile' variant release:
- Incompatible attribute:
- Required artifactType 'dll' and found incompatible value 'jar'.
- Other compatible attributes:
- Found buildType 'release' but wasn't required.
- Provides usage 'api'
Or:
> No variants of project : match the consumer attributes:
- Configuration ':myElements' declares attribute 'color' with value 'blue':
- Incompatible because this component declares attribute 'artifactType' with value 'jar' and the consumer needed attribute 'artifactType' with value 'dll'
- Configuration ':myElements' variant secondary declares attribute 'color' with value 'blue':
- Incompatible because this component declares attribute 'artifactType' with value 'jar' and the consumer needed attribute 'artifactType' with value 'dll'
In these cases, potentially compatible candidate variants are displayed, showing:
-
Incompatible attributes: Listed first to help identify why a variant could not be selected.
-
Other attributes: Including requested and compatible attributes, and any extra producer attributes that the consumer did not request.
The goal here is to understand which variant could be selected, if any.
In some cases, there may simply be no compatible variants from the producer (for example, if the consumer requires a dll
but the producer only offers a jar
or if a library is built for Java 11, but the consumer requires Java 8).
Dealing with incompatible variant errors
An incompatible variant error looks like the following example, where a consumer wants to select a variant with color=green
, but the only variant available has color=blue
:
> Could not resolve all dependencies for configuration ':resolveMe'. > Could not resolve project :. Required by: project : > Configuration 'mismatch' in project : does not match the consumer attributes Configuration 'mismatch': - Incompatible because this component declares attribute 'color' with value 'blue' and the consumer needed attribute 'color' with value 'green'
It occurs when Gradle cannot select a single variant of a dependency because an explicitly requested attribute value does not match (and is not compatible with) the value of that attribute on any of the variants of the dependency.
A sub-type of this failure occurs when Gradle successfully selects multiple variants of the same component, but the selected variants are incompatible with each other.
This looks like the following, where a consumer wants to select two different variants of a component, each supplying different capabilities, which is acceptable.
Unfortunately one variant has color=blue
and the other has color=green
:
> Could not resolve all dependencies for configuration ':resolveMe'. > Could not resolve project :. Required by: project : > Multiple incompatible variants of org.example:nyvu:1.0 were selected: - Variant org.example:nyvu:1.0 variant blueElementsCapability1 has attributes {color=blue} - Variant org.example:nyvu:1.0 variant greenElementsCapability2 has attributes {color=green} > Could not resolve project :. Required by: project : > Multiple incompatible variants of org.example:pi2e5:1.0 were selected: - Variant org.example:pi2e5:1.0 variant blueElementsCapability1 has attributes {color=blue} - Variant org.example:pi2e5:1.0 variant greenElementsCapability2 has attributes {color=green}
Dealing with ambiguous transformation errors
ArtifactTransforms can be used to transform artifacts from one type to another, changing their attributes. Variant selection can use the attributes available as the result of an artifact transform as a candidate variant.
If a project registers multiple artifact transforms, needs to use an artifact transform to produce a matching variant for a consumer’s request, and multiple artifact transforms could each be used to accomplish this, then Gradle will fail with an ambiguous transformation error like the following:
> Could not resolve all dependencies for configuration ':resolveMe'. > Found multiple transforms that can produce a variant of project : with requested attributes: - color 'red' - shape 'round' Found the following transforms: - From 'configuration ':roundBlueLiquidElements'': - With source attributes: - color 'blue' - shape 'round' - state 'liquid' - Candidate transform(s): - Transform 'BrokenTransform' producing attributes: - color 'red' - shape 'round' - state 'gas' - Transform 'BrokenTransform' producing attributes: - color 'red' - shape 'round' - state 'solid'
Visualizing variant information
Gradle offers built-in tasks to visualize the variant selection process and display the producer and consumer attributes involved.
Outgoing variants report
The report task outgoingVariants
shows the list of variants available for selection by consumers of the project. It displays the capabilities, attributes and artifacts for each variant.
This task is similar to the dependencyInsight
reporting task.
By default, outgoingVariants
prints information about all variants.
It offers the optional parameter --variant <variantName>
to select a single variant to display.
It also accepts the --all
flag to include information about legacy and deprecated configurations, or --no-all
to exclude this information.
Here is the output of the outgoingVariants
task on a freshly generated java-library
project:
> Task :outgoingVariants -------------------------------------------------- Variant apiElements -------------------------------------------------- API elements for the 'main' feature. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-api Artifacts - build/libs/lib.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes -------------------------------------------------- Description = Directories containing compiled class files for main. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api Artifacts - build/classes/java/main (artifactType = java-classes-directory) -------------------------------------------------- Variant mainSourceElements (i) -------------------------------------------------- Description = List of source directories contained in the Main SourceSet. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = verification - org.gradle.dependency.bundling = external - org.gradle.verificationtype = main-sources Artifacts - src/main/java (artifactType = directory) - src/main/resources (artifactType = directory) -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Artifacts - build/libs/lib.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes -------------------------------------------------- Description = Directories containing compiled class files for main. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-runtime Artifacts - build/classes/java/main (artifactType = java-classes-directory) -------------------------------------------------- Secondary Variant resources -------------------------------------------------- Description = Directories containing the project's assembled resource files for use at runtime. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = resources - org.gradle.usage = java-runtime Artifacts - build/resources/main (artifactType = java-resources-directory) -------------------------------------------------- Variant testResultsElementsForTest (i) -------------------------------------------------- Description = Directory containing binary results of running tests for the test Test Suite's test target. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = verification - org.gradle.testsuite.name = test - org.gradle.testsuite.target.name = test - org.gradle.testsuite.type = unit-test - org.gradle.verificationtype = test-results Artifacts - build/test-results/test/binary (artifactType = directory) (i) Configuration uses incubating attributes such as Category.VERIFICATION. (*) Secondary variants are variants created via the Configuration#getOutgoing(): ConfigurationPublications API which also participate in selection, in addition to the configuration itself.
From this you can see the two main variants that are exposed by a java library, apiElements
and runtimeElements
.
Notice that the main difference is on the org.gradle.usage
attribute, with values java-api
and java-runtime
.
As they indicate, this is where the difference is made between what needs to be on the compile classpath of consumers, versus what’s needed on the runtime classpath.
It also shows secondary variants, which are exclusive to Gradle projects and not published.
For example, the secondary variant classes
from apiElements
is what allows Gradle to skip the JAR creation when compiling against a java-library
project.
Information about invalid consumable configurations
A project cannot have multiple configurations with the same attributes and capabilities. In that case, the project will fail to build.
In order to be able to visualize such issues, the outgoing variant reports handle those errors in a lenient fashion. This allows the report to display information about the issue.
Resolvable configurations report
Gradle also offers a complimentary report task called resolvableConfigurations
that displays the resolvable configurations of a project, which are those which can have dependencies added and be resolved. The report will list their attributes and any configurations that they extend. It will also list a summary of any attributes which will be affected by Compatibility Rules or Disambiguation Rules during resolution.
By default, resolvableConfigurations
prints information about all purely resolvable configurations.
These are configurations that are marked resolvable but not marked consumable.
Though some resolvable configurations are also marked consumable, these are legacy configurations that should not have dependencies added in build scripts.
This report offers the optional parameter --configuration <configurationName>
to select a single configuration to display.
It also accepts the --all
flag to include information about legacy and deprecated configurations, or --no-all
to exclude this information.
Finally, it accepts the --recursive
flag to list in the extended configurations section those configurations which are extended transitively rather than directly.
Alternatively, --no-recursive
can be used to exclude this information.
Here is the output of the resolvableConfigurations
task on a freshly generated java-library
project:
> Task :resolvableConfigurations -------------------------------------------------- Configuration annotationProcessor -------------------------------------------------- Description = Annotation processors and their dependencies for source set 'main'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Description = Compile classpath for source set 'main'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Description = Runtime classpath of source set 'main'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Extended Configurations - implementation - runtimeOnly -------------------------------------------------- Configuration testAnnotationProcessor -------------------------------------------------- Description = Annotation processors and their dependencies for source set 'test'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime -------------------------------------------------- Configuration testCompileClasspath -------------------------------------------------- Description = Compile classpath for source set 'test'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api Extended Configurations - testCompileOnly - testImplementation -------------------------------------------------- Configuration testRuntimeClasspath -------------------------------------------------- Description = Runtime classpath of source set 'test'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Extended Configurations - testImplementation - testRuntimeOnly -------------------------------------------------- Compatibility Rules -------------------------------------------------- Description = The following Attributes have compatibility rules defined. - org.gradle.dependency.bundling - org.gradle.jvm.environment - org.gradle.jvm.version - org.gradle.libraryelements - org.gradle.plugin.api-version - org.gradle.usage -------------------------------------------------- Disambiguation Rules -------------------------------------------------- Description = The following Attributes have disambiguation rules defined. - org.gradle.category - org.gradle.dependency.bundling - org.gradle.jvm.environment - org.gradle.jvm.version - org.gradle.libraryelements - org.gradle.plugin.api-version - org.gradle.usage
From this you can see the two main configurations used to resolve dependencies, compileClasspath
and runtimeClasspath
, as well as their corresponding test configurations.