What does a variant inside Gradle Module Metadata carry, and how do dependencies vs. dependencyConstraints differ within it?
answer
- variant = attributes + capabilities + files + deps
- dependencies = real edges
- dependencyConstraints = no edge, version-only
- rich versions: strictly/require/prefer/reject
- platforms ship constraints, no files
basics
~10 sA variant carries attributes, capabilities, files, and two dependency lists: dependencies (modules this variant actually needs) and dependencyConstraints (version recommendations/limits applied only if that module is pulled in by someone else).
solid answer
~40 sWithin GMM, each variant is a self-contained unit: it has typed **attributes** (e.g. `org.gradle.usage`), optional **capabilities**, the **files** it exposes, and dependency information split into two lists. `dependencies` are real edges — modules the variant requires and that get added to the graph. `dependencyConstraints` are *not* edges: they express version preferences/limits (rich versions like `strictly`, `require`, `prefer`, `reject`) that only take effect if that module appears in the graph for some other reason. This is the GMM encoding of a `constraints { }` block or a platform. Because constraints carry rich version info losslessly, a GMM-consuming build sees `strictly`/`reject` semantics that a POM (which has no equivalent) would have flattened away. That is a key reason platforms published with GMM behave more predictably than POM-only BOMs.
code
kotlin · 12 lines// Producer side: this constraints { } block is published into the .module
// as a dependencyConstraint (NOT a dependency edge).
dependencies {
constraints {
api("com.fasterxml.jackson.core:jackson-databind") {
version {
strictly("2.15.3")
reject("2.16.0")
}
}
}
}go deeper
Recall that a variant has attributes, files, and dependencies; constraints are version hints.
Clearly separate edge-adding dependencies from non-edge dependencyConstraints.
Explain rich versions surviving in GMM and how platforms are constraints-only variants.
Reason about org-wide version alignment via GMM platforms and the migration cost from POM BOMs.
## Anatomy of a variant A variant in the `.module` file is an object with these main parts: - **`name`** — e.g. `apiElements`, `runtimeElements`. - **`attributes`** — typed key/value pairs (`org.gradle.usage`, `org.gradle.category`, `org.gradle.libraryelements`, `org.gradle.jvm.version`, ...). Consumers match these during variant-aware resolution. - **`capabilities`** — what the variant *provides*; used to detect conflicts (e.g. two modules both providing the same capability) and for feature variants. - **`files`** — the artifacts (name, url, size, checksums). - **`dependencies`** — outgoing edges. - **`dependencyConstraints`** — non-edge version influence. ## dependencies vs. dependencyConstraints This distinction is the crux: | | `dependencies` | `dependencyConstraints` | |---|---|---| | Adds a node to the graph? | **Yes** | **No** | | Effect | This variant *needs* the module | *If* the module is present (via someone else), use/limit this version | | Typical source | `implementation`/`api` declarations | `constraints { }`, a platform/BOM | A constraint can carry **rich versions**: ```json "dependencyConstraints": [ { "group": "com.fasterxml.jackson.core", "module": "jackson-databind", "version": { "strictly": "2.15.3", "reject": ["2.16.0"] } } ] ``` `strictly` forces exactly that version (and fails the build if another part of the graph demands something incompatible); `reject` blacklists versions. POMs cannot express this — they only have a single version string and `<dependencyManagement>` that is a soft override — so a constraint published via GMM is strictly more expressive. ## Why it matters - A **platform** (`org.gradle.category=platform`) published with GMM ships only `dependencyConstraints` and no files; consumers `implementation(platform("..."))` to align versions. - Because constraints are not edges, they let a library *recommend* a transitive version without forcing that transitive to be on the classpath. ## Rich version keys `requires`, `prefers`, `strictly`, `rejects` — all survive a GMM round-trip; the POM equivalent loses the nuance.
- Does a dependencyConstraint pull the module onto the classpath?No. A constraint only influences the version *if* the module is already in the graph for another reason; it never adds an edge by itself.
- Why can a GMM constraint express things a POM cannot?GMM carries rich versions (strictly/require/prefer/reject) losslessly, whereas a POM has only a plain version string and soft dependencyManagement overrides.
- How is a Gradle platform represented in GMM?As a variant with attribute org.gradle.category=platform that contains dependencyConstraints and no files; consumers apply it via platform("...").
saying these in an interview costs you the question
- Saying a dependencyConstraint adds the module to the classpath — it does not.
- Claiming a POM's <dependencyManagement> is equivalent to GMM's strictly/reject — POM overrides are soft and lossy.