When you publish a library, how do rich version declarations affect downstream consumers, and what governance concerns does that raise?
answer
- published into Gradle Module Metadata (.module)
- consumers inherit strictly/reject
- POM is a lossy approximation for Maven
- blast radius / constraints as API
- platforms = right place for strict policy
basics
~20 sRich 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 sRich 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// In a published library — be conservative
dependencies {
api("org.slf4j:slf4j-api") {
version {
require("[2.0, 3.0[") // floor + range, not strict
}
}
}go deeper
Know that publishing writes rich versions into module metadata that consumers can see.
Explain consumers inherit strictly/reject and that the POM is a lossy fallback for Maven users.
Discuss blast radius, library-vs-app responsibilities, and constraints-as-API when publishing.
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