Version Catalogs, Platforms, Constraints, Dependency Locking, and Centralized Version Governance: Concepts, Architecture, and Mental Model
Separate dependency declaration ergonomics from graph policy and resolved-state capture so a central version file never becomes a false promise of reproducibility.
Learning objectives
- Distinguish version-catalog aliases, platform/constraint policy, and dependency lock state by the exact build state each mechanism controls.
-
Explain why a version listed in
libs.versions.tomlis a requested version, not an immutable resolution result. - Compare regular platforms, strict constraints, preferred/rejected versions, and enforced platforms without treating them as interchangeable.
- Explain how Gradle dependency locking captures selected module versions and how lock state participates in later resolution.
- Identify the source-controlled governance files, machine-local caches, repository metadata, and CI evidence that must remain separate.
- Inspect dependency intent and resolved selection before changing governance policy.
1. The practical problem: one “version file” cannot do four jobs
Chapter 19 showed that dependency resolution is a graph-and-variant decision, not a string lookup. Chapter 20 adds governance. Teams often begin by centralizing coordinates in one file and then assume the build is “locked.” That assumption is dangerous because four different questions are being collapsed:
-
How do authors spell a dependency? A version
catalog can provide aliases such as
libs.commons.lang3. - What versions are acceptable or recommended? Platforms and constraints participate in resolution policy.
-
What version did this build actually select?
Dependency reports and
dependencyInsightshow the graph decision. - What exact selection must subsequent builds reproduce? Dependency locking records resolved module versions in lock state.
Chapter invariant: declaration convenience, resolution policy, and resolved-state capture are separate layers. A review should be able to point to the file/evidence for each layer.
2. Baseline and terminology
The mandatory path uses Gradle 9.7.1 through the
project Wrapper, JDK 21 to run Gradle, Java 17 as the course target,
Maven Central only for public dependency examples, and a disposable
GRADLE_USER_HOME. No external plugin is required.
| Object | Beginner-safe meaning | Typical evidence |
|---|---|---|
| Version catalog |
A named catalog of aliases, coordinates, versions, bundles,
and plugin aliases. The default file is
gradle/libs.versions.toml.
|
TOML + generated libs.* accessors. |
| Platform |
A dependency component that contributes dependency
constraints to the graph. A
java-platform project can publish Gradle
metadata and a Maven BOM.
|
Platform build script + dependency graph. |
| Constraint | A version requirement that participates only if the module is present; it does not add the module by itself. | Platform/build script + insight selection reasons. |
| Rich version |
A version expression using require,
strictly, prefer, and/or
reject.
|
Declared constraint and dependency insight. |
| Lock state | A source-controlled record of resolved module versions for configurations where locking is active. | Per-project gradle.lockfile. |
| Dependency verification | Integrity/provenance checking for artifacts and metadata. It is not dependency locking. | Covered later in Chapter 28; do not conflate it with versions. |
3. Four governance layers and their data flow
flowchart TD
C["libs.versions.toml aliases"] --> D["Dependency declarations"]
P["Platform + constraints"] --> R["Resolution engine"]
D --> R
M["Repository metadata"] --> R
L["Existing gradle.lockfile"] --> R
R --> S["Selected graph"]
S --> W["--write-locks / --update-locks"]
W --> L2["Reviewed lock state"]
S --> A["Artifact files in cache"]
The catalog supplies convenient dependency notation. The dependency declaration and platform constraints become graph inputs. Repository metadata contributes transitive requirements. Existing lock state constrains resolution. The selected graph can then be persisted as updated lock state. Artifact bytes are downloaded into the Gradle dependency cache, which is machine-local state and is not the governance source of truth.
4. Version catalogs centralize declaration intent, not final selection
Gradle automatically recognizes
gradle/libs.versions.toml and exposes type-safe
libs accessors. A catalog entry can carry an explicit
version, a rich version, or only a module coordinate. But a catalog
request enters ordinary resolution: another dependency, a
constraint, a platform, or lock state can change the selected
version.
[versions]
lang3 = "3.19.0"
[libraries]
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "lang3" }
slf4j-api.module = "org.slf4j:slf4j-api"
dependencies {
implementation(libs.commons.lang3)
implementation(libs.slf4j.api) // intentionally versionless: a platform can supply it
}
If a catalog requests Lang 3.19.0 and a compatible graph constraint selects 3.20.0, Gradle can select 3.20.0. That is why the official Gradle documentation explicitly says catalogs do not enforce versions during conflict resolution.
5. Platforms and constraints are graph policy
The java-platform plugin creates a component whose
primary payload is dependency constraints rather than Java classes.
Consumers add it with platform(project(":platform")) or
platform("group:bom:version"). The platform then
participates in graph selection, including transitive dependencies.
plugins { `java-platform` }
dependencies {
constraints {
api("org.apache.commons:commons-lang3") {
version { strictly("3.19.0") }
because("validated platform baseline")
}
api("org.slf4j:slf4j-api:2.0.17")
}
}
A regular platform(...) preserves ordinary conflict
semantics while importing constraints.
enforcedPlatform(...) overrides other version requests
and exports that force transitively, so Gradle recommends
caution—especially for reusable libraries. Prefer explicit
constraints where consumers need room to negotiate compatibility.
6. Rich versions express different strengths of opinion
| Term | Meaning in Gradle resolution | Typical governance use |
|---|---|---|
require |
The selected version must satisfy the requirement, but a higher compatible conflict winner can still be chosen. Ordinary shorthand versions are required versions. | Minimum/range policy where compatible upgrades are acceptable. |
strictly |
The strongest declaration. Versions outside the strict expression are excluded; incompatible strict opinions can make resolution fail. | A tested compatibility boundary or explicit downgrade/freeze. |
prefer |
A soft preference used only when no stronger non-dynamic opinion determines the result. | Suggest a known-good version inside an allowed range. |
reject |
Marks specific versions/ranges unacceptable. | Exclude known-bad releases while preserving flexibility elsewhere. |
Strictness is not automatically “better.” A library that exports a strict constraint can restrict downstream consumers. Governance should document why the strength is justified and how a consumer can diagnose incompatibility.
7. Dependency locking captures selected state
Locking is activated per resolvable configuration or broadly with
lockAllConfigurations(). After activation, running a
resolution command with --write-locks creates or
updates gradle.lockfile. Gradle documents lock state as
source-controlled input and applies locked versions like strict
constraints during later resolution.
dependencyLocking {
lockAllConfigurations()
}
./gradlew :app:dependencies --write-locks
sed -n '1,120p' app/gradle.lockfile
Two cautions matter. First, only configurations actually resolved during the command can contribute lock state; “locking enabled” does not mean every possible configuration was resolved. Second, locks capture versions, not immutable artifact contents. A changing module such as a SNAPSHOT can keep the same coordinates while its bytes change, which is why Gradle warns against treating dependency locking as a solution for changing dependencies.
8. Read-only inspection before mutation
Before editing the catalog, platform, or lockfiles, establish the current model and resolution result:
./gradlew --version
java -version
./gradlew projects
./gradlew :app:dependencies --configuration runtimeClasspath
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath
sed -n '1,160p' gradle/libs.versions.toml
find . -name gradle.lockfile -type f -print
The first five commands identify the tool/JVM, project graph, resolved dependency graph, and selection reasons. The final two show declaration governance and captured lock state without regenerating anything.
9. Files, caches, repositories, and trust boundaries
| State | Typical location | Authoritative for | Not authoritative for |
|---|---|---|---|
| Catalog | gradle/libs.versions.toml |
Aliases/requested versions | Final resolved graph |
| Platform policy |
platform/build.gradle.kts or imported BOM
|
Constraints/alignment policy | Which dependencies are actually declared |
| Lock state | */gradle.lockfile |
Previously selected module versions | Artifact byte integrity |
| Build declarations | build.gradle(.kts) |
Which dependencies/platforms/configurations are connected | Repository artifact authenticity |
| Dependency cache | GRADLE_USER_HOME/caches |
Reusable local resolution/artifact state | Release identity or governance |
| Remote repository | Maven/Ivy repository | Published metadata/artifacts | Your organization’s accepted policy by itself |
Catalog and lockfile changes are code-review material. Repository credentials, dependency-verification metadata, plugin repositories, and wrapper checksums are separate security-sensitive boundaries; this chapter does not hide them inside “version management.”
10. Common wrong mental models
- “The catalog pins everything.” No: it creates dependency requests/accessors.
- “A constraint adds a dependency.” No: it only influences the module if that module enters the graph.
- “A platform and a lockfile are interchangeable.” No: the platform declares policy; the lockfile captures a resolved state.
- “enforcedPlatform is simply a stronger BOM.” It exports force-like behavior transitively and can be hostile to reusable-library consumers.
- “A lockfile proves the JAR is trustworthy.” No: integrity verification is a different control.
11. DevOps operating model
A production dependency-governance review should preserve at least four kinds of evidence: the declaration diff (catalog/build files), the policy diff (platform/constraints), the resolved graph/selection reasons, and the lockfile diff. A bot that changes all four in one opaque commit defeats the auditability centralization was supposed to create. Chapter 20 therefore treats “update dependency” as a controlled state transition rather than a search-and-replace.
Knowledge check
Why can libs.versions.toml say 3.19.0 while
dependencyInsight reports 3.20.0?
Because the catalog supplied a requested version, while normal Gradle conflict resolution, a platform/constraint, or lock state selected a different compatible version.
Does a platform constraint on module X add X to the graph?
No. A constraint influences X only if X is otherwise brought into the graph.
What does a gradle.lockfile capture?
Resolved module versions for locked configurations that were resolved when lock state was written; it does not prove artifact byte integrity.
When is enforcedPlatform risky?
Especially in reusable libraries, because its forced versions are transitive and can override downstream consumers’ version choices.
Which is stronger: prefer or
strictly?
strictly. prefer is soft; a strict
expression excludes versions outside its accepted set.
A CI build has a warm cache and passes. Does that prove lock governance works from a clean agent?
No. Re-run with an isolated Gradle User Home and inspect the source-controlled lock state/resolved graph; a warm cache is not policy evidence.
Official references and version notes
- Gradle Version Catalogs — alias/accessor semantics, TOML format, rich versions, publishing/sharing.
- Using Catalogs with Platforms — explains why catalogs do not enforce graph versions and how platforms differ.
- Gradle Platforms and Java Platform Plugin — constraints, BOM imports, regular versus enforced platforms.
-
Declaring Versions and Ranges
—
require,strictly,prefer, andreject. -
Dependency Locking
— activation,
--write-locks,--update-locks, lockfile location, and lock modes. - Viewing and Debugging Dependencies — dependency reports and selection reasons.
- Gradle 9.7.1 Release Notes — pinned chapter baseline.
Version-sensitive statements were rechecked for Gradle 9.7.1 on 2026-08-24. This chapter does not depend on Maven 4, a third-party Gradle plugin, hosted CI, or a commercial repository.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.