Chapter 20Lesson 05~245 minutes

Checkpoint Lab — Version Catalogs, Platforms, Constraints, Dependency Locking, and Centralized Version Governance

Govern a small multi-project dependency set end to end, capture lock evidence, perform one minimal reviewed upgrade, inject an out-of-policy version, and prove the gate catches it.

CheckpointMulti-projectMinimal lock diffPolicy gateRollback

Learning objectives

  • Build a three-project Gradle governance fixture with one version catalog, one Java platform, and lock state in two consumers.
  • Predict which files represent declaration intent, resolution policy, and resolved state before running Gradle.
  • Capture initial dependencyInsight and lockfile evidence for a clean baseline.
  • Perform a reviewed Commons Lang 3.19.0 → 3.20.0 platform upgrade with a targeted minimal lock update.
  • Inject a direct out-of-policy strict version and prove graph resolution rejects it.
  • Verify rollback/cleanup and articulate the production operating model carried into Chapter 21.

1. Checkpoint scenario and success criteria

Your team owns a small multi-project JVM product. Build authors should write stable aliases, compatibility policy should live in one platform, and CI should reproduce previously reviewed selections. You must prove each layer independently and show that an incompatible direct request cannot bypass the platform.

Success means: the catalog contains no duplicate platform version, both consumers resolve through the platform, lockfiles are committed candidates, a single reviewed policy upgrade produces an explainable lock diff, and an out-of-policy strict request fails resolution.

2. Preflight and exact assumptions

Wrapper rule: start from a disposable copy of a project whose Gradle 9.7.1 Wrapper was already reviewed in Chapter 15. Do not generate a new wrapper from an unknown system Gradle merely for this lab. Keep GRADLE_USER_HOME inside the disposable lab and remove only that lab state during cleanup.

Assumption Checkpoint value
Gradle 9.7.1 through committed Wrapper
Gradle runtime JVM JDK 21 in the course environment; Gradle 9 requires JVM 17+
Project target Java 17 toolchain
Repository Maven Central only for public dependencies
Initial policy Commons Lang 3.19.0 strict; SLF4J API 2.0.17 required
Reviewed upgrade Commons Lang 3.20.0
Cache Project-local disposable GRADLE_USER_HOME
Paid/hosted services None
cd gradle-governance-checkpoint
export GRADLE_USER_HOME="$PWD/.gradle-user-home"
./gradlew --version
java -version

3. Create the governance files

// settings.gradle.kts
dependencyResolutionManagement {
    repositories { mavenCentral() }
}
rootProject.name = "gradle-governance-checkpoint"
include("platform", "lib", "app")
# gradle/libs.versions.toml
[libraries]
commons-lang3.module = "org.apache.commons:commons-lang3"
slf4j-api.module = "org.slf4j:slf4j-api"
// platform/build.gradle.kts
plugins { `java-platform` }

dependencies {
    constraints {
        api("org.apache.commons:commons-lang3") {
            version { strictly("3.19.0") }
            because("approved checkpoint baseline")
        }
        api("org.slf4j:slf4j-api:2.0.17")
    }
}
// lib/build.gradle.kts
plugins { `java-library` }
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
dependencies {
    api(platform(project(":platform")))
    api(libs.commons.lang3)
}
dependencyLocking { lockAllConfigurations() }
// app/build.gradle.kts
plugins { application }
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
dependencies {
    implementation(platform(project(":platform")))
    implementation(project(":lib"))
    implementation(libs.slf4j.api)
}
application { mainClass = "dev.academy.app.App" }
dependencyLocking { lockAllConfigurations() }

Add the same small Java sources from Lesson 2 (Words using StringUtils, and App calling it). The exact source behavior is not the checkpoint; the dependency graph is.

4. Predict before resolution

Prediction Expected reason
libs.versions.toml contains no Lang version Platform owns graph policy; catalog owns coordinate alias.
:lib selects Lang 3.19.0 Its versionless alias is constrained by the strict platform.
:app also sees Lang 3.19.0 transitively through :lib The app consumes the same platform and project dependency.
No gradle.lockfile exists before --write-locks in a fresh fixture Lock activation does not itself persist state.
Changing platform strict 3.19.0 → 3.20.0 without updating locks will fail Existing lock state acts like a strict opinion at 3.19.0.

Write these predictions into evidence/predictions.txt before running resolution. The checkpoint requires prediction/evidence pairs, not retrospective explanations.

5. Capture the initial graph and lock state

mkdir -p evidence
./gradlew :lib:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath | tee evidence/lib-lang.initial.txt
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath | tee evidence/app-lang.initial.txt
./gradlew :lib:dependencies :app:dependencies --write-locks | tee evidence/locks.initial.log
cp lib/gradle.lockfile evidence/lib.initial.lock
cp app/gradle.lockfile evidence/app.initial.lock
./gradlew :app:build

Verify the platform is cited in selection reasons, Lang is 3.19.0, SLF4J is 2.0.17, and the lockfiles contain the external modules for the configurations resolved by the dependency reports.

6. Reviewed upgrade with minimal lock intent

Edit one policy line in platform/build.gradle.kts: strictly("3.20.0").

set +e
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath > evidence/stale-lock.expected-failure.txt 2>&1
stale=$?
set -e
printf 'stale lock exit=%s\n' "$stale"

./gradlew :lib:dependencies :app:dependencies --update-locks org.apache.commons:commons-lang3 | tee evidence/locks.targeted-update.log

diff -u evidence/lib.initial.lock lib/gradle.lockfile > evidence/lib.lock.diff || true
diff -u evidence/app.initial.lock app/gradle.lockfile > evidence/app.lock.diff || true
cat evidence/lib.lock.diff
cat evidence/app.lock.diff
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath | tee evidence/app-lang.upgraded.txt
./gradlew :app:build

Verification: the resolved Lang version is now 3.20.0, the platform is the policy source, and the lock diff is limited to changes causally required by that upgrade. If another module moves, explain why using insight before accepting it.

7. Inject an out-of-policy version and prove the gate catches it

Temporarily add this dependency to app/build.gradle.kts:

implementation("org.apache.commons:commons-lang3") {
    version { strictly("3.18.0") }
    because("intentional out-of-policy checkpoint injection")
}
set +e
./gradlew :app:dependencies --configuration runtimeClasspath > evidence/out-of-policy.txt 2>&1
policy_status=$?
set -e
printf 'out-of-policy exit=%s\n' "$policy_status"
sed -n '1,220p' evidence/out-of-policy.txt

# Repair: remove the temporary direct strict dependency, then verify again.
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath
./gradlew :app:build

Expected: the platform strictly requires 3.20.0 while the injected direct dependency strictly requires 3.18.0. The accepted-version sets do not intersect, so resolution fails. That failure is the governance gate working. Do not “fix” it with an exclusion or force.

8. Explain declaration versus resolution in one sentence per file

File What it controls
gradle/libs.versions.toml Stable aliases/coordinates used by build authors; no final version is encoded for this checkpoint.
platform/build.gradle.kts Compatibility/version policy that participates in graph resolution.
lib/build.gradle.kts / app/build.gradle.kts Which platform and dependency edges each project actually declares, plus locking activation.
lib/gradle.lockfile / app/gradle.lockfile Resolved external module versions captured for locked, resolved configurations.
evidence/*.txt Human-review proof of selection reasons/failures; useful lab/CI artifact, not input to Gradle resolution.

9. Verification checklist

  • Wrapper reports Gradle 9.7.1 and the expected JDK.
  • Catalog aliases are consumed in :lib/:app and do not duplicate the Lang policy version.
  • Both consumers depend on the local platform.
  • Initial insight selects Lang 3.19.0; upgraded insight selects 3.20.0.
  • Initial lockfiles are saved before mutation.
  • Targeted update uses --update-locks org.apache.commons:commons-lang3.
  • Every lock diff line is explained.
  • The out-of-policy strict 3.18.0 injection fails resolution.
  • After repair, :app:build succeeds again.
  • No credentials, normal user cache, or hosted service was modified.

10. Cleanup and rollback

# Confirm the temporary 3.18.0 injection has been removed.
grep -R '3.18.0' app platform gradle || true

# The final reviewed state should retain the 3.20.0 policy and its matching locks.
./gradlew :app:dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath

cd ..
rm -rf gradle-governance-checkpoint

For a real repository, rollback means reverting the platform and lockfile commit together—not deleting normal caches. This disposable cleanup removes only the checkpoint directory.

11. Production operating model and bridge to Chapter 21

Chapter 20 adds a three-layer dependency-governance model: catalogs make declarations consistent, platforms/constraints make compatibility policy explicit, and lockfiles make selected state reviewable. The next chapter expands the structural boundary: multi-project and composite builds determine where these policies live, how included builds interact, and how build ownership scales without turning one root script into a global mutable namespace.

Knowledge check

Which file in the checkpoint controls the approved Lang version?

Why is the stale-lock failure before the targeted update required evidence?

Why use --update-locks org.apache.commons:commons-lang3 instead of deleting lockfiles?

What should happen when app adds strict Lang 3.18.0 while the platform strictly requires 3.20.0?

Does a successful final build prove artifact authenticity?

What is the Chapter 21 bridge?

Official references and version notes

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.