skip to content

Walk through the structure of a `.module` file: what does a 'variant' entry contain, and how does Gradle use it during resolution?

level: middleimportance: must knowfreq 45%

answer

  1. component / createdBy / variants array
  2. variant = name + attributes + dependencies + capabilities + files
  3. attributes = usage/category/libraryelements/bundling
  4. variant-aware matching at resolve time
  5. outgoingVariants task to inspect

basics

~20 s

Each variant in the .module JSON has a name, a map of attributes, a list of dependencies (and constraints), capabilities, and the files it provides. Gradle matches the consumer's requested attributes against these to pick one variant.

solid answer

~40 s

A `.module` file has a `component` block (group/module/version), a `createdBy` block, and a `variants` array. Each variant carries: a `name` (e.g. `apiElements`); an `attributes` map such as `org.gradle.usage=java-api`, `org.gradle.category=library`, `org.gradle.libraryelements=jar`, `org.gradle.dependency.bundling=external`; a `dependencies` list with coordinates and version constraints (`requires`, `prefers`, `strictly`, `rejects`); a `dependencyConstraints` list; a `capabilities` list; and `files` describing the artifacts (name, url, size, checksums). At resolution time the consumer requests a set of attributes via its resolvable configuration; Gradle's **variant-aware engine** finds the single variant whose attributes are compatible/best-match, then brings in that variant's dependencies, constraints, capabilities, and files. If no variant matches uniquely, resolution fails with an ambiguity/no-match error listing the candidate variants and attributes.

code

bash · 5 lines
bash
# Inspect the outgoing variants a project will write into its .module file
./gradlew outgoingVariants

# See why a dependency resolved to a given variant
./gradlew dependencyInsight --configuration runtimeClasspath --dependency guava

go deeper

for a junior

Recall that a variant has a name, attributes, dependencies, and files.

for a middle

Enumerate the variant fields accurately and explain that attributes drive selection while dependencies/files are the payload of the chosen variant.

for a senior

Explain compatibility/disambiguation, capabilities-as-conflict, and how to debug no-match/ambiguity errors via the printed candidate attributes.

for a principal

Reason about designing a consistent attribute schema across many modules so cross-project variant matching stays unambiguous at scale.

## Top-level shape A `.module` file is JSON with these top-level keys: - `formatVersion` — the GMM schema version (e.g. `1.1`). - `component` — `{ group, module, version }` plus optional attributes. - `createdBy` — provenance (the Gradle version that produced it). - `variants` — the heart of the file: an array of variant objects. ## Inside one variant ```json { "name": "apiElements", "attributes": { "org.gradle.category": "library", "org.gradle.dependency.bundling": "external", "org.gradle.libraryelements": "jar", "org.gradle.usage": "java-api" }, "dependencies": [ { "group": "com.google.guava", "module": "guava", "version": { "requires": "33.0.0-jre" } } ], "dependencyConstraints": [ ... ], "capabilities": [ ... ], "files": [ { "name": "lib-1.0.jar", "url": "lib-1.0.jar", "size": 1234, "sha256": "..." } ] } ``` Key parts: - **attributes** — the *coordinates in the variant dimension*. The standard ones come from the `Usage`, `Category`, `LibraryElements`, `Bundling`, and (for the JVM) `TargetJvmVersion` attributes. They are what Gradle matches against. - **dependencies** — the dependencies that *this* variant pulls in. Versions use the rich constraint model: `requires`, `prefers`, `strictly`, plus `rejects`. - **dependencyConstraints** — versions to enforce on transitive deps *if/when* they appear, without requiring them directly. - **capabilities** — what this variant *provides*; used to detect and reject two modules that offer the same capability (conflict). - **files** — the actual artifacts (name, relative url, size, checksums) Gradle downloads if this variant is chosen. ## How resolution uses it The consumer side has a **resolvable configuration** (e.g. `compileClasspath`) whose requested attributes describe what it needs (`usage=java-api`, the right JVM version, etc.). Gradle's **variant-aware matching** compares those requested attributes against each variant's `attributes`, using compatibility and disambiguation rules. The single best-matching variant wins; its dependencies/constraints/capabilities/files are then incorporated into the graph. If zero variants match, or several match ambiguously, resolution fails with a diagnostic that prints the requested attributes and each candidate variant's attributes — the canonical way to debug "no variant matches" errors. ## Generating and inspecting The file is produced by the `GenerateModuleMetadata` task. You can inspect a component's outgoing variants locally with `./gradlew outgoingVariants`, which mirrors what gets written into the `.module` file.

  • What happens if two variants both match the requested attributes?
    Gradle tries disambiguation rules; if it still can't pick one it fails with an ambiguity error listing the candidate variants and their attributes.
  • How do you see a project's outgoing variants before publishing?
    Run `./gradlew outgoingVariants`, which lists each consumable configuration, its attributes, artifacts, and capabilities — the same data serialized into the `.module` file.

saying these in an interview costs you the question

  • Saying all dependencies live in one global list — in GMM dependencies are per-variant.
  • Confusing attributes (matching keys) with capabilities (conflict detection).

context