Chapter 19Lesson 04~205 minutes

Gradle Configurations, Dependency Resolution, Variants, Attributes, Capabilities, and Metadata Rules: Diagnostics, Failure Modes, Security, and Performance

Diagnose wrong variants, unsafe configuration-time resolution, capability conflicts, overly broad metadata rules, and interoperability gaps by inspecting the effective request, available producer variants, metadata source, and selected graph before changing caches or repositories.

DiagnosticsResolution timingMetadata scopeSupply chainPerformance

Diagnose wrong variants, unsafe configuration-time resolution, capability conflicts, overly broad metadata rules, and interoperability gaps by inspecting the effective request, available producer variants, metadata source, and selected graph before changing caches or repositories.

Learning objectives

  • Use an evidence-first sequence to distinguish graph, attribute, metadata, repository, cache, and compiler/runtime failures.
  • Diagnose missing/incomplete attributes without inventing arbitrary disambiguation rules.
  • Recognize and remove configuration-time resolution.
  • Repair capability conflicts semantically rather than hiding them with exclusions.
  • Scope metadata rules narrowly and detect unintended graph-wide effects.
  • Diagnose interoperability gaps where Maven consumers cannot reproduce Gradle-only variant semantics.

1. Diagnostic sequence: preserve the model before editing it

  1. Preserve the first concise failure and exact command.
  2. Confirm ./gradlew --version and Java identity.
  3. Identify the configuration being resolved and its requested attributes.
  4. Inspect producer outgoing variants/capabilities or external module metadata source.
  5. Run dependencies/dependencyInsight for the affected edge.
  6. Separate repository/cache transport errors from selection errors.
  7. Inspect task/compiler/test failure only after the graph is proven.
  8. Apply the least broad correction.
  9. Verify under a controlled isolated User Home if cache state is suspect.

2. Failure: wrong variant is selected because the request is incomplete

Suppose a producer exposes standard and instrumented variants but the consumer does not request the flavor attribute. Multiple candidates may remain compatible or Gradle may disambiguate using other attributes in a way the team did not intend.

Diagnosis:

./gradlew :consumer:resolvableConfigurations --configuration academyRuntime
./gradlew :producer:outgoingVariants
./gradlew :consumer:dependencyInsight --configuration academyRuntime --dependency producer --all-variants

Repair: declare the missing consumer requirement or define a reviewed compatibility/disambiguation rule only if the domain genuinely has compatible values. Do not rename a variant and expect selection to change.

3. Failure: resolving a configuration during configuration time

This pattern is a red flag:

// Intentionally bad: resolution occurs while the build model is being configured.
val runtimeFiles = configurations.named("runtimeClasspath").get().files
println("runtime file count = ${runtimeFiles.size}")

It can trigger metadata/artifact resolution for unrelated task requests and can surface unsafe-resolution problems in more isolated/parallel builds. Replace it with a task input/provider or resolve inside the execution action when the task actually needs files:

val runtimeClasspath = configurations.named("runtimeClasspath")
tasks.register("reportRuntime") {
    inputs.files(runtimeClasspath)
    doLast {
        println("runtime file count = ${runtimeClasspath.get().files.size}")
    }
}

4. Failure: capability conflict is “fixed” by exclusion

A conflict message that says multiple components provide the same capability is valuable evidence. An arbitrary exclude can silence the message by deleting one dependency edge, but another path may reintroduce the provider and the semantic choice remains undocumented.

Prefer a capability-resolution rule with a reason, or remove one direct implementation dependency if it truly should not be there.

configurations.named("runtimeClasspath") {
    resolutionStrategy.capabilitiesResolution
        .withCapability("dev.academy:logging-binding") {
            selectHighestVersion()
            because("organization-approved binding selection")
        }
}

5. Failure: one metadata rule silently changes every consumer

Compare these intentions:

dependencies {
    components {
        // Risky when the problem belongs to one known module:
        all<MyRule>()

        // Prefer a proven scope:
        withModule<MyRule>("dev.vendor:broken-module")
    }
}

Broad rules can alter graph semantics for modules never involved in the original failure. They also expand rule execution cost. Scope first; centralize later only when the policy is intentionally universal and covered by tests.

6. Failure: the team assumes GMM semantics but consumes POM-only metadata

If a repository contains only a POM, Gradle derives conventional variants. Custom producer capabilities/attributes that existed only in a .module file are unavailable. The consumer may select a generic Maven-derived runtime variant or fail to enforce a conflict the Gradle producer intended.

Inspect repository files and repository metadataSources configuration. Do not invent a cache problem when the richer metadata was never published or was deliberately disabled.

7. Failure: a Maven consumer cannot interpret a Gradle-only contract

This is not a Maven bug. If a Gradle publication requires a custom attribute/capability to select the correct binary, a Maven POM cannot communicate that general rule. A Maven consumer will see the POM's conventional artifacts/scopes. Production options include publishing interoperable separate coordinates/classifiers, keeping one conventional main artifact, or limiting the rich variant contract to Gradle consumers.

8. Cache and repository diagnosis without destructive cleanup

When metadata seems stale, first reproduce under a second disposable User Home:

export GRADLE_USER_HOME="$PWD/.gradle-user-home-fresh"
./gradlew :consumer:dependencyInsight --configuration legacyRuntime --dependency legacy-lib --refresh-dependencies

--refresh-dependencies is a targeted revalidation control, not a reason to delete normal ~/.gradle. Compare the original and fresh evidence. If both select the same wrong variant, fix the model rather than the cache.

9. Debug logging is evidence with a disclosure cost

--info or --debug can reveal repository URLs, environment details, and request flow. Use the least verbose level that answers the question. Never publish raw debug logs containing credential headers, tokens, private repository paths, or sensitive environment data. Redact before attaching CI evidence.

10. Performance: measure the correct phase

Separate:

  • configuration/model time;
  • graph/metadata resolution;
  • artifact download/cache lookup;
  • compile/test/package work.

A metadata rule affects resolution; a warm Gradle User Home mainly changes download/metadata lookup; a custom attribute may change which artifact is selected but not necessarily graph size. Do not attribute every speed change to “Gradle caching.”

11. Intentionally broken example: request a nonexistent flavor

From Lesson 2:

set +e
./gradlew :consumer:showSelectedArtifact -PacademyFlavor=does-not-exist > bad-variant.txt 2>&1
status=$?
set -e
printf 'status=%s\n' "$status"

Interpret the failure: the consumer requested a typed attribute value for which no compatible producer variant exists. The fix is not --refresh-dependencies, a repository change, or an exclusion. Restore a supported value or intentionally add a new producer variant with a documented contract.

12. Compact runbook

Symptom First evidence Least broad fix
Unexpected artifact/classpath Consumer attributes + outgoing variants + insight Correct missing/wrong attribute request.
Resolution happens on help Build script stack/problem + config-time code Move file resolution into task/provider execution.
Two implementations conflict Capability conflict report + dependency paths Explicit capability selection or remove unnecessary direct provider.
Unrelated graphs changed after rule Search components.all; compare insight Scope rule with withModule and add tests.
Maven consumer gets different behavior Compare POM vs .module and consumed artifacts Publish an interoperable contract or document Gradle-only support.
Only warm workspace succeeds Repeat with isolated User Home Fix missing repository/metadata declaration; do not depend on cached state.

Knowledge check

Why is requesting a nonexistent custom attribute value not a cache problem?

What is wrong with reading configuration.files at top level?

Why is components.all risky for a one-module defect?

What should you inspect when GMM behavior appears missing?

When is a capability conflict a success of the model?

What is the safe cache test?

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.