Chapter 19Lesson 01~195 minutes

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.

Gradle 9.7.1ConfigurationsVariantsAttributesCapabilities

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

Resolution model
  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/runtimeClasspath are 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?

Does a variant name participate in attribute matching?

What problem do capabilities solve that coordinates alone cannot?

Why can a Maven consumer lose information from a Gradle publication?

What is the first diagnostic command pair for an attribute-selection surprise?

Why prefer withModule over a broad metadata rule?

Official references and version notes

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.