Gradle Configurations, Dependency Resolution, Variants, Attributes, Capabilities, and Metadata Rules: Concepts, Architecture, and Mental Model
Move beyond coordinates and scopes: understand which Gradle configurations declare, resolve, or expose dependencies; how consumer attributes select producer variants; how capabilities detect mutually exclusive implementations; and where metadata repair belongs.
Move beyond coordinates and scopes: understand which Gradle configurations declare, resolve, or expose dependencies; how consumer attributes select producer variants; how capabilities detect mutually exclusive implementations; and where metadata repair belongs.
Learning objectives
- Distinguish declarable, resolvable, and consumable configuration roles and explain why one configuration should normally have one role.
- Trace resolution from a dependency declaration through graph resolution, variant matching, and artifact selection.
- Explain how attributes represent consumer requirements and producer characteristics, including compatibility and disambiguation.
- Use capabilities to model mutually exclusive providers rather than hiding conflicts with arbitrary exclusions.
- Compare Gradle Module Metadata with Maven POM-derived metadata and identify semantics that Maven cannot represent.
- Explain when a narrowly scoped component metadata rule is justified and why broad repair rules are a trust/performance risk.
1. The practical problem: the same coordinate can expose more than one meaning
Earlier chapters deliberately separated plugin code, task graphs,
and ordinary dependency declarations. A dependency such as
group:name:version is still not enough to explain what
Gradle finally puts on a classpath. A Java library may expose API
and runtime variants; a platform may expose constraints rather than
a JAR; a component may publish multiple target-JVM variants; and two
unrelated coordinates may provide the same capability.
Gradle therefore resolves more than a list of coordinates. It resolves a consumer request against a producer component model. The configuration being resolved contributes attributes; candidate producer variants contribute their own attributes and capabilities; module metadata describes those candidates; and the engine selects a compatible variant before resolving its artifact files.
Chapter invariant: when a dependency result surprises you, do not begin with cache deletion or exclusions. First identify which configuration was resolved, which attributes it requested, which variants/capabilities were available, which metadata format supplied them, and why one candidate won or resolution failed.
2. Baseline and vocabulary before mutation
The mandatory path remains Gradle 9.7.1 through the
project Wrapper, JDK 21 to run Gradle, Java 17 as the course JVM
target, and a disposable GRADLE_USER_HOME. No
third-party plugin is required.
| Term | Beginner-safe meaning | Observable evidence |
|---|---|---|
| Configuration | A named dependency-model bucket with a role, dependencies, attributes, and/or outgoing artifacts. |
resolvableConfigurations,
dependencies, build script.
|
| Declarable |
A bucket used to declare dependency intent, such as Java
implementation. It should not itself be
resolved or consumed.
|
Role flags and configuration report. |
| Resolvable |
A consumer-side configuration that can turn dependency
intent into a resolved graph and artifact files, such as
runtimeClasspath.
|
Attributes +
dependencies/dependencyInsight.
|
| Consumable |
A producer-side configuration/variant exposed to other
consumers, such as runtimeElements.
|
outgoingVariants. |
| Variant | One consumable form of a component, described by attributes/capabilities and artifacts. |
Outgoing variant report or GMM .module file.
|
| Attribute | A typed key/value used to express requirements or characteristics. | Resolvable/outgoing reports and insight. |
| Capability | A versioned statement that a component/variant provides a particular feature. | Outgoing variants and capability-conflict messages. |
| Component metadata rule | Consumer-side code that repairs/enriches downloaded module metadata before resolution. | Declared rule + changed insight/selection evidence. |
3. Three configuration roles are different jobs, not three synonyms
Gradle 9.7.1 documents three roles. A
declarable configuration captures intent. A
resolvable configuration asks for concrete
dependencies/artifacts. A consumable configuration exposes
artifacts/metadata to other projects. The Java plugins create
familiar examples: implementation is declarable,
runtimeClasspath is resolvable, and
runtimeElements is consumable.
| Role | Typical flags | Java example | What not to do |
|---|---|---|---|
| Declarable | canBeDeclared=true; others false |
implementation |
Do not iterate its files or publish it. |
| Resolvable | canBeResolved=true; others false |
runtimeClasspath |
Do not use it as a bucket for arbitrary declarations. |
| Consumable | canBeConsumed=true; others false |
runtimeElements |
Do not resolve it to obtain your own classpath. |
The role-specific configuration factory
methods—dependencyScope(), resolvable(),
and consumable()—are convenient because they encode
intent, but Gradle currently labels these factory methods
incubating. This chapter shows their meaning and
also keeps the stable role flags visible so you can recognize older
builds.
4. Dependency resolution is a model pipeline
flowchart TD
D["Declarable dependency intent"] --> R["Resolvable configuration"]
R --> G["Graph resolution"]
M["Module metadata"] --> G
G --> V["Variant candidates"]
A["Consumer attributes"] --> V
V --> S["Compatibility + disambiguation"]
S --> C["Capabilities/conflict handling"]
C --> F["Artifact files"]
The dependency declaration feeds a resolvable configuration. Metadata contributes component versions, variants, dependencies, attributes, capabilities, and artifact references. The consumer configuration supplies requested attributes. Gradle first constructs/selects graph nodes, then performs variant-aware matching and artifact resolution. Capabilities can make otherwise different coordinates conflict because they claim the same feature.
5. Attributes are typed requirements, and variant names do not select variants
A producer can expose variants named apiElements,
runtimeElements, linuxX64, or anything
else. The name appears in reports, but the selection algorithm uses
the attributes, not the name. Typical JVM
attributes include usage (java-api versus
java-runtime), category, library elements, bundling,
and target JVM version.
Matching has two concepts:
- Compatibility: can a producer value satisfy the requested value?
- Disambiguation: if more than one candidate is compatible, which compatible value should win?
Gradle ecosystems register standard rules. Custom attributes are possible, but custom compatibility/disambiguation rules become part of your build protocol and can reduce interoperability if they escape a bounded context.
6. Capabilities model “these components provide the same thing”
Coordinates identify modules; capabilities identify functionality. Every component has an implicit capability based on its coordinates. You can also declare explicit capabilities. If two selected components provide the same capability, Gradle normally fails instead of silently placing mutually exclusive implementations together.
This is materially different from an exclusion. An exclusion says “remove this dependency edge.” A capability says “these candidates compete to provide one feature.” The latter preserves the semantic conflict and lets the consumer make an explicit selection.
configurations.configureEach {
resolutionStrategy.capabilitiesResolution
.withCapability("dev.academy:logging-binding") {
selectHighestVersion()
because("only one logging binding may be present")
}
}
7. GMM and POM are not equivalent metadata formats
Gradle Module Metadata (GMM), stored as a
.module file, serializes Gradle's component model and
natively carries variants, attributes, capabilities, rich version
constraints, and dependency constraints. A Maven
pom.xml carries Maven's dependency model. Gradle can
derive variants when consuming a POM, but that derivation cannot
recreate semantics the POM never contained.
| Semantic | Gradle Module Metadata | Maven POM |
|---|---|---|
| Variant-aware model | Native | Derived by Gradle from Maven scopes/packaging conventions |
| Attributes | Native | No general equivalent |
| Capabilities | Native | No equivalent |
| Rich version constraints | Native | Limited mapping |
| Maven consumers | Not normally interpreted by Maven | Native |
| Best use | Gradle-rich consumers and precise variants | Broad Maven ecosystem interoperability |
When Gradle publishes to a Maven repository, it commonly publishes both GMM and a POM. That dual publication is a compatibility bridge, not proof that every rich Gradle semantic has a Maven representation.
8. Metadata rules are a repair layer, not a universal policy hammer
A component metadata rule runs after metadata is obtained but before
Gradle uses it for resolution. It can adjust variants, attributes,
capabilities, dependencies, constraints, and published files. The
official guidance favors isolated
@CacheableRule classes and
withModule("group:name", Rule::class) scope for
module-specific repairs.
Use a rule only after proving the producer metadata is incomplete or incorrect. If you control the producer, fixing and republishing correct metadata is usually stronger because every consumer benefits. A consumer-only rule is invisible to other builds and therefore becomes local policy debt.
9. Read-only inspection comes before changing selection
Start with evidence. The following commands do not need to mutate build logic:
./gradlew --version
java -version
./gradlew projects
./gradlew :app:resolvableConfigurations --configuration runtimeClasspath
./gradlew :library:outgoingVariants
./gradlew :app:dependencies --configuration runtimeClasspath
./gradlew :app:dependencyInsight --configuration runtimeClasspath --dependency example
outgoingVariants shows producer
attributes/capabilities/artifacts.
resolvableConfigurations shows consumer attributes.
dependencies shows the graph.
dependencyInsight explains one selected dependency and
its reason. The optional --all-variants insight flag is
currently incubating, so production automation should not assume its
output/API is frozen.
10. Files, state, and trust boundaries
Keep these stores distinct:
| State | Typical location | Why it matters |
|---|---|---|
| Build declarations |
settings.gradle.kts,
build.gradle.kts
|
Reviewable intent, repositories, attributes, rules. |
| Gradle project state | project .gradle/ |
Ephemeral execution/configuration state; not producer metadata. |
| Gradle User Home | isolated GRADLE_USER_HOME in labs |
Downloaded metadata/artifacts and caches; warm state can hide network/source differences. |
| Producer output | build/, outgoing variants |
Artifacts and generated metadata. |
| Repository metadata | .module, .pom, checksums |
The trust input that describes external components. |
| CI evidence | dependency reports, checksums, logs | What reviewers use to prove actual resolution. |
A repository is a trust boundary because metadata can redirect the graph to additional code, and artifacts become compiler/runtime inputs. Component metadata rules are also executable build logic and deserve code review.
11. Common wrong mental models
-
“implementation is a classpath.” It is a
declaration bucket;
compileClasspath/runtimeClasspathare the resolvable classpaths. - “runtimeElements wins because of its name.” Names are diagnostic; attributes drive matching.
- “Exclude fixes duplicate implementations.” It may hide one path without modeling the mutually exclusive capability.
- “A POM and .module file are equivalent.” They can describe the same coordinate but different semantic richness.
- “Metadata rules are harmless.” They rewrite the consumer's interpretation of external components and can change every graph they target.
12. DevOps operating model
For CI, dependency-resolution governance should preserve four artifacts of reasoning: the configuration being resolved, its requested attributes, the selected producer variant/capability, and the metadata/repository source. That evidence makes changes reviewable and lets teams distinguish a graph-policy change from a cache/network problem.
Chapter 20 will add version catalogs, platforms, constraints, and lock state. Those mechanisms govern versions and declarations; they do not replace the variant/capability model learned here.
Knowledge check
Why is implementation usually not the
configuration you should resolve directly?
It is a declarable dependency bucket. Resolvable configurations such as compileClasspath/runtimeClasspath extend from it and add consumer attributes appropriate to a real classpath.
Does a variant name participate in attribute matching?
No. The name is primarily diagnostic. Producer and consumer attributes, plus compatibility/disambiguation rules, determine selection.
What problem do capabilities solve that coordinates alone cannot?
They can state that different coordinates provide the same functionality, allowing Gradle to detect and force an explicit choice between mutually exclusive providers.
Why can a Maven consumer lose information from a Gradle publication?
Maven POM metadata has no general representation for Gradle attributes, capabilities, and arbitrary rich variants.
What is the first diagnostic command pair for an attribute-selection surprise?
Inspect the consumer with resolvableConfigurations and the producer with outgoingVariants, then use dependencyInsight for the selected edge.
Why prefer withModule over a broad metadata
rule?
It limits the repair to the component whose metadata problem was actually proven, reducing hidden graph changes and rule cost elsewhere.
Official references and version notes
- Gradle 9.7.1 Release Notes — pinned Gradle baseline; released August 19, 2026.
- Creating Dependency Configurations — declarable, resolvable, consumable roles and role flags. The role-specific factory methods are currently incubating.
- Dependency Resolution and Variant Selection and Attribute Matching.
- Variants and Attributes — standard/custom attributes, compatibility and disambiguation.
- Capabilities — conflicting providers and capability resolution.
- Modifying Dependency Metadata — narrowly scoped, cacheable component metadata rules.
- Gradle Module Metadata and Metadata Formats — GMM, Maven POM, Ivy, and interoperability limits.
-
Viewing Dependencies
—
dependenciesanddependencyInsight;--all-variantsis currently incubating. - Compatibility Matrix — Gradle currently requires JVM 17 through 26 to execute.
Version snapshot: Generated August 24, 2026 with Gradle 9.7.1, JDK 21 as the Gradle runtime, Java 17 as the course target, only core Gradle APIs, a verified project Wrapper, and project-local Gradle User Homes. Re-check incubating APIs before standardizing them.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.