Version Catalogs, Platforms, Constraints, Dependency Locking, and Centralized Version Governance: Diagnostics, Failure Modes, Security, and Performance
Diagnose stale lock state, accidental broad updates, dynamic selectors, conflicting strict constraints, and divergent subproject policy without hiding evidence or deleting normal caches.
Learning objectives
- Diagnose a version-catalog expectation that differs from the resolved graph without assuming the catalog is broken.
- Interpret stale lock failures and distinguish intentional targeted updates from accidental broad lock regeneration.
- Identify why dynamic selectors and changing modules weaken review expectations even when locking is present.
- Repair incompatible strict constraints using graph evidence rather than force/exclusion guesswork.
- Detect subprojects that have escaped centralized platform/catalog policy.
- Use isolated caches and concise diagnostics without deleting normal Gradle state or leaking sensitive settings.
1. Evidence-first diagnostic sequence
Use the same production sequence established earlier in the course:
- Preserve concise failure output and lock/catalog/platform diffs.
- Confirm Wrapper Gradle and runtime JDK identity.
- Inspect declared catalog/platform/build configuration.
-
Inspect the relevant resolvable configuration with
dependencies/dependencyInsight. - Inspect lockfiles and repository/cache state without mutating them.
- Interpret the constraint/lock conflict or resolution message.
- Apply the narrowest correction.
- Re-resolve in a controlled run and verify the lock diff plus build result.
2. Failure: “the catalog says 3.19.0, so why did 3.20.0 win?”
This is not necessarily a failure at all. A catalog version creates a dependency request. Another direct/transitive request or platform constraint may legitimately select 3.20.0.
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath
Read the selection reasons. “By conflict resolution,” “By constraint,” “Forced,” or lock-related failures tell you which policy layer actually changed the result. Repair the policy only if the selected result violates documented compatibility—not merely because it differs from the TOML text.
3. Failure: platform upgraded, lockfile still captures the old version
With a strict platform constraint at 3.20.0 and a lock at 3.19.0, resolution can become unsatisfiable because both are strict opinions. Preserve that failure: it proves the lock is preventing silent drift.
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath --stacktrace
# after review of the policy change:
./gradlew :app:dependencies --update-locks org.apache.commons:commons-lang3
Do not solve this by deleting the lockfile and running a broad build. Update the specific module after reviewing the policy change, then inspect all resulting lock diffs.
4. Failure: a broad lock update changes far more than expected
--write-locks overwrites lock state for configurations
resolved in that invocation. If someone runs it after changing
multiple selectors, many entries can move. Preserve the old
lockfiles, compare them, and reproduce selection with
dependencyInsight. If the intended change was one
module, restore the unrelated lock state and retry with
--update-locks group:module.
CI policy: ordinary verification pipelines should
not include --write-locks. A successful build that
silently mutates source governance is not a verification build.
5. Failure: a dynamic selector defeats human review expectations
[libraries]
# Diagnostic anti-pattern for production governance:
lang3-dynamic = { module = "org.apache.commons:commons-lang3", version = "3.+" }
Dependency locking can stabilize the selected version after resolution, but the declaration still says “choose something from a moving set.” That can be deliberate for an exploratory lane, yet it weakens review intent and requires disciplined lock updates. Changing versions (for example snapshots whose bytes can change under the same coordinates) are worse: a version lock cannot make the content immutable.
6. Intentionally broken example: incompatible strict constraints
Start from a platform that strictly accepts 3.20.0, then temporarily
add this direct dependency in :app:
dependencies {
implementation("org.apache.commons:commons-lang3") {
version { strictly("3.18.0") }
because("intentional Chapter 20 conflict")
}
}
set +e
./gradlew :app:dependencies --configuration runtimeClasspath > evidence/strict-conflict.txt 2>&1
status=$?
set -e
printf 'strict-conflict status=%s\n' "$status"
sed -n '1,200p' evidence/strict-conflict.txt
Expected: resolution fails because no version can satisfy both strict opinions. Do not add an exclusion, force, or cache deletion. Remove the accidental direct strict constraint—or change the platform policy only if the compatibility requirement itself was wrong—then rerun insight and the build.
7. Failure: one subproject is outside the platform policy
A multi-project build can appear centralized while one subproject
declares implementation(libs.commons.lang3) without
also consuming platform(project(":platform")). Compare
subproject dependency reports and inspect build scripts. The narrow
fix is to apply the platform consistently—preferably through
reviewed convention logic—rather than duplicate versions into the
stray subproject.
8. Cache/repository diagnostic: prove the model before touching normal state
# Keep the normal user cache untouched.
export GRADLE_USER_HOME="$PWD/.fresh-gradle-home"
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath --refresh-dependencies
A fresh user home separates repository/download state from the
committed catalog/platform/lock model. It can be slower and may
require network access, so use it only when cache/repository state
is actually in question. Never recommend recursively deleting the
user’s normal ~/.gradle as the first diagnostic step.
9. Security and log boundaries
Catalog/platform/lockfiles are usually safe to commit, but
dependency repositories may require credentials and may expose
private package names. Debug logs can reveal repository URLs,
environment details, and server configuration. Keep credentials in
Gradle’s supported credential/property mechanisms, use fake values
in labs, and do not paste full --debug output into
public tickets without review. Version governance is not a
substitute for dependency verification; Chapter 28 handles
checksum/signature policy.
10. Performance: measure the phase that changed
A cold isolated home measures dependency download/metadata cost as well as graph resolution; a warm run measures mostly model and graph work. Locking can reduce version-selection ambiguity but does not remove repository access for missing artifacts. Do not attribute a slow cold build to “lockfiles” unless timings and logs show lock processing is the cause.
Knowledge check
A catalog requests 3.19.0 and insight selects 3.20.0 by constraint. Is the catalog corrupt?
No. Catalog versions are requests; constraints participate in normal graph selection.
Why is a stale-lock failure after a reviewed platform upgrade useful?
It prevents the selected graph from changing until lock state is intentionally updated and reviewed.
Why not delete the lockfile to fix a stale lock?
That erases the captured state/evidence and broadens the change. Use a targeted lock update after reviewing the policy change.
What does a conflict between strict 3.20.0 and strict 3.18.0 mean?
The graph has mutually incompatible accepted-version sets; resolution should fail until one policy is corrected.
Does locking make a changing/SNAPSHOT module immutable?
No. The same coordinates can refer to changing bytes; version locking cannot guarantee content identity.
When should you try an isolated Gradle User Home?
After model/graph evidence suggests repository/cache state may be involved—not as a blind first fix.
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.