skip to content

How are dependency constraints and rich versions (strictly/prefers/rejects) represented and used through Gradle Module Metadata?

level: middleimportance: should knowfreq 35%

answer

  1. constraint = conditional version, no edge added
  2. rich version: requires/prefers/strictly/rejects
  3. per-variant dependencyConstraints in GMM
  4. POM can't encode strictly/rejects
  5. java-platform publishes constraints

basics

~10 s

GMM stores per-variant dependencyConstraints and rich version info (requires, prefers, strictly, rejects) as structured JSON. Consumers that read GMM honor them during resolution; the POM can only approximate this.

solid answer

~40 s

A dependency **constraint** says "if this module appears in the graph, use (at least/exactly) this version" without forcing it to be present. In GMM each variant has a `dependencyConstraints` array, and both dependencies and constraints carry a structured `version` object with `requires`, `prefers`, `strictly`, and `rejects`. A Gradle consumer reading the `.module` file applies these faithfully: `strictly` downgrades or fails conflicting requests, `prefers` is a soft hint, `rejects` blacklists versions, and constraints influence transitive selection. This is a key reason to publish GMM: the Maven POM cannot express strict/reject semantics, so when GMM is absent Gradle falls back to the POM and these rules are lost (only approximated via `<dependencyManagement>` for some constraints). Constraints in GMM also power platform/BOM-style alignment when you publish a `java-platform` component.

code

kotlin · 10 lines
kotlin
dependencies {
    constraints {
        api("org.apache.commons:commons-lang3:3.14.0") {
            because("CVE fix; force consumers off older lines")
        }
    }
    implementation("io.netty:netty-all") {
        version { strictly("4.1.108.Final") }
    }
}

go deeper

for a junior

Know constraints exist and that rich versions (strict/prefer/reject) are stored in GMM.

for a middle

Differentiate dependency vs constraint, explain the four version keys, and note GMM is required to preserve strict/reject end-to-end.

for a senior

Discuss conflict-resolution effects (downgrade, fail) and the regression risk of disabling GMM on constraint-reliant libraries.

for a principal

Govern org-wide version policy via published platforms/constraints, weighing the trade-off for Maven-only consumers who don't read GMM.

## Dependencies vs constraints - A **dependency** says "I need module X" and brings it into the graph. - A **dependency constraint** says "*if* module X is in the graph (directly or transitively), it must satisfy this version rule" — it does **not** by itself pull X in. You declare them with the `constraints { }` block. Both are recorded **per variant** in GMM (`dependencies` and `dependencyConstraints`). ## Rich versions GMM serializes versions as a structured object rather than a bare string: - `requires` — the version you need (range or exact). - `prefers` — a soft preference used only to break ties. - `strictly` — a hard rule: the resolved version *must* satisfy it, otherwise resolution fails or other requests are downgraded. - `rejects` — versions that are explicitly forbidden. ```kotlin dependencies { implementation("com.squareup.okhttp3:okhttp") { version { strictly("4.12.0") reject("4.9.0") // known-bad } } constraints { implementation("com.google.guava:guava:33.0.0-jre") { because("align all consumers on a vetted Guava") } } } ``` ## How a consumer applies them When a downstream project resolves your published module and reads the `.module` file, Gradle's conflict resolution honors these rules: `strictly` can **downgrade** an otherwise-higher transitive version (or fail with a clear conflict if two strict requests disagree), `rejects` removes candidate versions, and constraints raise the floor without adding edges. This gives publishers real control over consumers' transitive graphs. ## Why GMM matters here The Maven POM has **no** concept of `strictly` or `rejects`, and only a partial concept of constraints (`<dependencyManagement>`). So these semantics survive end-to-end **only** when GMM is published and the consumer is Gradle. If you suppress or disable GMM, a Gradle consumer falls back to the POM and silently loses strict/reject behavior — a subtle correctness regression. This is why disabling GMM on a library that relies on rich versions is discouraged.

  • Does a constraint add a dependency edge to the graph?
    No. A constraint only influences the version *if* the module is already present (directly or transitively). It never introduces the module on its own.
  • What is lost if a Gradle consumer falls back to your POM instead of your `.module`?
    Strict/reject semantics and full rich-version/constraint fidelity — the POM can't encode them, so the consumer gets only the approximate POM view.

saying these in an interview costs you the question

  • Claiming a constraint pulls the dependency into the graph.
  • Asserting the POM can fully represent `strictly`/`rejects` (it cannot).

context