skip to content

When you publish a library, how do rich version declarations affect downstream consumers, and what governance concerns does that raise?

level: seniorimportance: nice to knowfreq 22%

answer

  1. published into Gradle Module Metadata (.module)
  2. consumers inherit strictly/reject
  3. POM is a lossy approximation for Maven
  4. blast radius / constraints as API
  5. platforms = right place for strict policy

basics

~20 s

Rich versions (strictly, reject, etc.) are written into Gradle Module Metadata when you publish, so consumers inherit your constraints. A strict constraint in your library can force or fail a consumer's resolution, so publish them deliberately.

solid answer

~50 s

Rich version declarations don't stay local — when you publish a library, Gradle serializes them into the **Gradle Module Metadata** (`.module` file) alongside the POM. Consumers using Gradle then see your `strictly`, `prefer`, `reject`, and `require` intents and apply them during *their* resolution. That's powerful but risky: a `strictly` you ship can **downgrade or fail** a consumer's build if it conflicts with their other dependencies, and a `reject`/`rejectAll()` propagates your blacklist to everyone downstream. Maven consumers (no module metadata support) see a degraded view via the POM. The governance concern is blast radius: strict constraints in a widely-used library remove flexibility from every consumer, so libraries generally prefer `require`/`prefer` and reserve `strictly`/`reject` for genuine compatibility or security necessities — often documented with `because(...)`. Application/platform projects, which sit at the top of the graph, are the right place for aggressive strict pinning.

code

kotlin · 8 lines
kotlin
// In a published library — be conservative
dependencies {
    api("org.slf4j:slf4j-api") {
        version {
            require("[2.0, 3.0[") // floor + range, not strict
        }
    }
}

go deeper

for a junior

Know that publishing writes rich versions into module metadata that consumers can see.

for a middle

Explain consumers inherit strictly/reject and that the POM is a lossy fallback for Maven users.

for a senior

Discuss blast radius, library-vs-app responsibilities, and constraints-as-API when publishing.

for a principal

Define org policy: centralize strict/reject in platforms, govern published constraints as a versioned contract, and weigh downstream resolution impact.

## Where rich versions go on publish Gradle publishes a **Gradle Module Metadata** file (`module.json`, the `.module` artifact) in addition to the Maven POM. Rich version info — `require`, `strictly`, `prefer`, `reject`, and `because` reasons — lives in that metadata. The POM, being a simpler format, can only approximate it (e.g. strict often maps to a fixed `[x]`-style range, and not all nuance survives). ## What consumers experience - **Gradle consumers** with metadata enabled (default) honor your constraints during their own resolution. Your `strictly` becomes a hard constraint *in their graph*; if it conflicts with something they need, **their** build fails. - **Maven (or metadata-disabled) consumers** get the POM's lossy approximation — they may not see `reject` or `prefer` at all, leading to different resolved versions than a Gradle consumer. ## Governance / blast radius A strict or reject constraint in a popular library propagates transitively. Examples of the tension: - Shipping `strictly("1.2")` on a transitive utility can prevent thousands of consumers from upgrading that utility independently. - `rejectAll()` on `commons-logging` in your library forces every consumer to avoid it too — fine as a deliberate stance, surprising if undocumented. Guidelines that interviewers like to hear: 1. **Libraries**: prefer `require`/`prefer`; use `strictly`/`reject` only for real correctness or security constraints, always with `because(...)`. 2. **Applications & platforms**: the top of the graph — the right place for opinionated `strictly` pinning and CVE blacklists, since nothing consumes them. 3. Treat published constraints as **API**: changing them is a potential breaking change for downstream resolution. ## Practical example ```kotlin // A platform (BOM-like) project — safe place for strict org policy dependencies { constraints { api("com.fasterxml.jackson.core:jackson-databind") { version { strictly("2.17.1") } because("org-approved jackson line; CVE-free") } } } ``` Consumers importing this platform inherit the policy centrally, which is the controlled way to apply org-wide rich-version governance.

  • Why might a Gradle consumer and a Maven consumer resolve different versions of your library's transitive deps?
    Gradle reads the richer .module metadata (reject/prefer/strictly), while Maven only sees the lossy POM approximation, so nuances like reject or prefer may be lost.
  • Where is aggressive strict pinning appropriate?
    At the top of the graph — applications and platform/BOM projects — because nothing consumes them, so the blast radius is contained.
  • Why treat published constraints as API?
    Downstream resolution depends on them; tightening or loosening a strict/reject constraint can change or break consumers' builds, so it's effectively a contract change.

saying these in an interview costs you the question

  • Assuming rich versions are local-only and never reach consumers
  • Putting strictly on transitive utilities in a widely-used library without justification
  • Forgetting Maven consumers get a degraded POM view

context