Checkpoint Lab — Build Automation Foundations, Reproducibility, Build Graphs, and the JVM Toolchain Ecosystem
Complete an evidence-driven checkpoint: build the same tiny JVM behavior with Maven and Gradle, record graph/tool/input identities, compare clean and repeat runs, perturb declared and undeclared inputs, and document what the experiment does—and does not—prove.
Learning objectives
- Execute controlled Maven and Gradle verification paths in disposable, isolated local state.
- Record JDK, wrapper/build-tool, source, dependency/model, test, artifact, and checksum evidence before comparing outcomes.
- Predict and verify at least two model, graph, cache, or artifact changes before performing them.
- Distinguish a declared source input change from an unrelated environment change and explain their expected effects.
- Produce a concise reproducibility report with assumptions, evidence, limitations, and cleanup rather than declaring success from a green build alone.
1. Checkpoint scenario and acceptance contract
You are handing a tiny JVM component to a CI platform team. They do not care which laptop created the first JAR. They need an explainable build contract: exact tool identities, declared inputs, test evidence, package identity, what is reusable, what is generated, and which assumptions remain outside the build model.
REQUIRED EVIDENCE
[ ] JDK/JVM identity recorded
[ ] Maven wrapper reports 3.9.16
[ ] Gradle wrapper reports 9.7.1
[ ] source SHA-256 values recorded
[ ] Maven effective model/dependency tree inspected
[ ] Gradle projects/tasks/dry-run inspected
[ ] two tests pass in each project
[ ] JAR contents inspected
[ ] artifact SHA-256 recorded for clean and repeat runs
[ ] repeat-run behavior interpreted, not merely observed
[ ] one declared source input changed and consequences predicted/verified
[ ] one unrelated environment variable changed and consequences predicted/verified
[ ] limitations of the reproducibility claim documented
[ ] disposable lab state cleaned without touching normal user caches
A checkpoint passes when the evidence is internally consistent and the learner can explain causality. Matching artifact digests are useful, but they do not replace test evidence or prove that every external input has been controlled.
2. Setup and preflight
Reuse the two disposable projects from Lesson 2, or recreate them
with the exact POM, Gradle files, Greeting.java, and
GreetingTest.java shown there. Do not run this lab in a
valuable repository. Keep the structure below.
build-foundations-lab/
├── .lab-state/
├── maven-app/
│ ├── .mvn/wrapper/...
│ ├── mvnw
│ ├── mvnw.cmd
│ ├── pom.xml
│ └── src/{main,test}/java/com/example/greeting/...
└── gradle-app/
├── gradle/wrapper/...
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
├── build.gradle.kts
└── src/{main,test}/java/com/example/greeting/...
cd build-foundations-lab
test -f maven-app/mvnw
test -f gradle-app/gradlew
java -version
maven-app/mvnw -v
gradle-app/gradlew --version
If a wrapper downloads a distribution on first use, treat that as a resolution event. Confirm the configured distribution URL and expected version before trusting the downloaded executable. Do not replace a wrapper property with an unreviewed mirror simply to bypass a network failure.
3. Record the input and version inventory
Create a small evidence directory outside both generated-output
directories. The report should survive clean but remain
inside the disposable lab.
mkdir -p evidence
{
echo "=== Java ==="
java -version 2>&1
echo
echo "=== Maven wrapper ==="
(cd maven-app && ./mvnw -v)
echo
echo "=== Gradle wrapper ==="
(cd gradle-app && ./gradlew --version)
} > evidence/tool-versions.txt
find maven-app/src gradle-app/src -type f -print0 \
| sort -z \
| xargs -0 sha256sum > evidence/source-before.sha256
cat evidence/tool-versions.txt
cat evidence/source-before.sha256
The source hashes provide exact identity for the lab’s authored Java files. They do not hash the POM, Gradle scripts, wrappers, dependencies, or JDK; those need their own evidence. This is why a reproducibility report should list its scope rather than saying “everything is reproducible.”
4. Prediction 1 — what should a clean build change?
Before executing anything, write down your prediction:
-
Maven
clean verifyshould recreatetarget/, run tests, and produce a Maven JAR. -
Gradle
clean buildshould recreatebuild/, execute the required Java/test/package task graph, and produce a Gradle JAR. - The isolated dependency/cache directories may populate with downloaded artifacts.
- Neither build should modify the Java source files.
That is your first required model/graph prediction. The next steps test it.
5. Maven controlled build and evidence
cd maven-app
./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" clean verify \
| tee ../evidence/maven-clean.log
jar tf target/greeting-maven-1.0.0.jar \
> ../evidence/maven-jar-contents.txt
sha256sum target/greeting-maven-1.0.0.jar \
> ../evidence/maven-clean.sha256
cp target/surefire-reports/*.txt ../evidence/
cd ..
Verify that the log shows the expected lifecycle reaching
verify, the test report records two tests with zero
failures/errors, and the JAR contains Greeting.class.
Do not accept the existence of the JAR as a substitute for the test
result.
6. Gradle controlled build and evidence
cd gradle-app
GRADLE_USER_HOME="$PWD/../.lab-state/gradle" \
./gradlew clean build --console=plain \
| tee ../evidence/gradle-clean.log
jar tf build/libs/greeting-gradle-1.0.0.jar \
> ../evidence/gradle-jar-contents.txt
sha256sum build/libs/greeting-gradle-1.0.0.jar \
> ../evidence/gradle-clean.sha256
cp build/test-results/test/TEST-*.xml ../evidence/
cd ..
Verify that test succeeds, the XML report contains the
expected tests, and the JAR contains the application class. The
Gradle console should show which tasks executed. This is
execution-graph evidence; it is not interchangeable with Maven
lifecycle logging.
7. Prediction 2 — what should a repeat build do?
Before repeating the build, predict the difference:
- Maven should reuse already downloaded dependencies from the isolated local repository but normally execute the selected lifecycle/plugin work again.
-
Gradle should recognize unchanged declared task inputs/outputs and
mark eligible work
UP-TO-DATE(or another non-executed outcome such asNO-SOURCE). - For each tool, if archive inputs and controlled metadata are stable, its repeated JAR checksum should remain the same.
Do not predict that the Maven JAR digest must equal the Gradle JAR digest. They are different build implementations and can create different archive metadata while packaging equivalent class behavior.
8. Run again without clean and compare
cd maven-app
./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" verify \
| tee ../evidence/maven-repeat.log
sha256sum target/greeting-maven-1.0.0.jar \
> ../evidence/maven-repeat.sha256
cd ../gradle-app
GRADLE_USER_HOME="$PWD/../.lab-state/gradle" \
./gradlew build --console=plain \
| tee ../evidence/gradle-repeat.log
sha256sum build/libs/greeting-gradle-1.0.0.jar \
> ../evidence/gradle-repeat.sha256
cd ..
diff -u evidence/maven-clean.sha256 evidence/maven-repeat.sha256 || true
diff -u evidence/gradle-clean.sha256 evidence/gradle-repeat.sha256 || true
grep -E "UP-TO-DATE|FROM-CACHE|NO-SOURCE" evidence/gradle-repeat.log || true
If a checksum changes unexpectedly, inspect the archive and build configuration rather than declaring the tool non-reproducible. If Gradle executes a task you predicted would be up-to-date, inspect which input or output changed. The value of the exercise is the explanation.
9. Declared-input perturbation: change the Java source
Now change a declared input in both projects. Replace the greeting
prefix from Hello to Welcome, and update
the corresponding test expectation. Before running, predict:
- source hashes will change;
- compilation and tests must no longer be reusable as unchanged work;
- the packaged artifact identity should change;
- dependency versions should not change merely because application source changed.
return "Welcome, " + name.trim() + "!";
assertEquals("Welcome, DevOps!", Greeting.message(" DevOps "));
find maven-app/src gradle-app/src -type f -print0 \
| sort -z \
| xargs -0 sha256sum > evidence/source-after-declared-change.sha256
(cd maven-app && ./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" verify)
(cd gradle-app && GRADLE_USER_HOME="$PWD/../.lab-state/gradle" ./gradlew build --console=plain)
sha256sum maven-app/target/greeting-maven-1.0.0.jar \
> evidence/maven-declared-change.sha256
sha256sum gradle-app/build/libs/greeting-gradle-1.0.0.jar \
> evidence/gradle-declared-change.sha256
Compare the new source and artifact hashes with the earlier evidence. This is the simplest causal chain in the chapter: a declared source/test input changed, the relevant execution work changed, and the package identity changed.
10. Unrelated environmental perturbation: change a value the build does not read
Set an environment variable named ACADEMY_NOISE.
Neither POM nor Gradle build reads this variable. Predict that it
should not change source hashes, test behavior, or artifact bytes.
export ACADEMY_NOISE="first"
(cd maven-app && ./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" verify)
(cd gradle-app && GRADLE_USER_HOME="$PWD/../.lab-state/gradle" ./gradlew build --console=plain)
sha256sum maven-app/target/greeting-maven-1.0.0.jar
sha256sum gradle-app/build/libs/greeting-gradle-1.0.0.jar
export ACADEMY_NOISE="second"
(cd maven-app && ./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" verify)
(cd gradle-app && GRADLE_USER_HOME="$PWD/../.lab-state/gradle" ./gradlew build --console=plain)
sha256sum maven-app/target/greeting-maven-1.0.0.jar
sha256sum gradle-app/build/libs/greeting-gradle-1.0.0.jar
The variable is environmental state, but it is not an input to these builds because nothing reads it. This distinction matters: “environment variable exists” is not equivalent to “environment variable affects output.” Lesson 4 showed the dangerous opposite case—a task that reads an environment value but fails to declare it.
11. Record model and graph evidence
A checksum-only report cannot explain why the build ran. Add read-only model evidence:
cd maven-app
./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" \
help:effective-pom > ../evidence/maven-effective-pom.xml
./mvnw -Dmaven.repo.local="$PWD/../.lab-state/m2" \
dependency:tree > ../evidence/maven-dependency-tree.txt
cd ..
cd gradle-app
GRADLE_USER_HOME="$PWD/../.lab-state/gradle" \
./gradlew projects > ../evidence/gradle-projects.txt
GRADLE_USER_HOME="$PWD/../.lab-state/gradle" \
./gradlew tasks > ../evidence/gradle-tasks.txt
GRADLE_USER_HOME="$PWD/../.lab-state/gradle" \
./gradlew build --dry-run > ../evidence/gradle-build-dry-run.txt
cd ..
The effective POM reveals inherited/defaulted Maven model state. The dependency tree records the resolved dependency relationship. Gradle project/task/dry-run views document the model and selected task path. None of these files should contain real credentials; review before sharing evidence from a non-lab project.
12. Write a reproducibility report with explicit limits
# Chapter 01 reproducibility checkpoint
## Controlled
- source/test files and their SHA-256 identities
- project build files
- Maven wrapper: 3.9.16
- Gradle wrapper: 9.7.1
- lab JVM/JDK: 21
- pinned JUnit/plugin versions in the example
- Maven local repository isolated to .lab-state/m2
- Gradle User Home isolated to .lab-state/gradle
- fixed archive timestamp intent in Maven; reproducible archive settings in Gradle
## Verified
- Maven tests pass and report files exist
- Gradle tests pass and report files exist
- both JARs contain Greeting.class
- clean/repeat artifact identities recorded
- declared source change changes expected work/artifact identity
- unrelated ACADEMY_NOISE variable does not affect this build
## Not proven by this lab
- rebuild on a second operating system or CPU architecture
- long-term immutability of every remote dependency/plugin repository
- organization repository-manager policy
- CI runner hardening or secret management
- publication/signing/provenance
- equivalence of Maven-produced and Gradle-produced JAR bytes
## Production next steps
- commit and verify wrapper integrity metadata
- enforce approved repositories and dependency governance
- pin/lock/verify dependencies according to tool policy
- execute the same wrapper entry point on ephemeral CI
- record exact artifact identity and promote the same bytes
A good report makes the negative space visible. “Not proven” is not failure; it prevents a small local experiment from being misrepresented as full supply-chain assurance.
13. Hash the evidence package
Hashing evidence does not make its claims true. It makes the reviewed evidence set identifiable so later changes are detectable.
find evidence -type f ! -name evidence-manifest.sha256 -print0 \
| sort -z \
| xargs -0 sha256sum > evidence/evidence-manifest.sha256
cat evidence/evidence-manifest.sha256
14. Windows notes for the checkpoint
On PowerShell use mvnw.cmd, gradlew.bat,
Get-FileHash, and a process-scoped
GRADLE_USER_HOME. Do not translate POSIX environment
syntax literally.
$env:GRADLE_USER_HOME = "$PWD\.lab-state\gradle"
Set-Location maven-app
.\mvnw.cmd "-Dmaven.repo.local=$PWD\..\.lab-state\m2" clean verify
Get-FileHash .\target\greeting-maven-1.0.0.jar -Algorithm SHA256
Set-Location ..
Set-Location gradle-app
.\gradlew.bat clean build --console=plain
Get-FileHash .\build\libs\greeting-gradle-1.0.0.jar -Algorithm SHA256
Set-Location ..
15. Verification checklist and cleanup
| Check | Pass condition |
|---|---|
| Tool identity | Wrapper Maven 3.9.16; wrapper Gradle 9.7.1; expected JDK 21 baseline |
| Model evidence | Maven effective POM/dependency tree and Gradle project/task/dry-run evidence retained |
| Tests | Two tests pass in each tool’s report |
| Artifacts | Each JAR inspected and SHA-256 recorded |
| Repeat run | Observed Maven/Gradle reuse behavior explained |
| Declared perturbation | Source/test hashes and relevant work/artifact identity change as predicted |
| Environmental perturbation |
ACADEMY_NOISE does not change output because
the build does not read it
|
| Safety | No real secrets/private endpoints; only disposable local state |
| Limitations | Report distinguishes verified claims from untested production boundaries |
pwd
find . -maxdepth 2 -type d | sort
# Keep evidence if you want to review it.
# When finished, move one directory up and verify the target:
# cd ..
# test "$(basename "$PWD")" != "build-foundations-lab"
# rm -rf build-foundations-lab
~/.m2, ~/.gradle, a production
repository checkout, shared artifact repository, or CI cache. Remove
only the verified disposable lab directory.
Knowledge check
The Maven and Gradle JARs have different SHA-256 values. Did the checkpoint fail?
No. Cross-tool byte identity was not the requirement. Verify each tool’s repeatability against its own controlled runs, inspect JAR contents and test evidence, and explain tool-specific archive differences.
The Gradle repeat build executes compileJava even
though you did not edit Java source. What should you do
first?
Inspect which declared input or output changed and the task outcome/details. Check toolchain, build configuration, generated inputs, and output state before forcing clean or deleting caches.
You changed ACADEMY_NOISE and the artifact
changed. What does that imply?
Something in the build is reading that variable directly or indirectly, or another uncontrolled input changed at the same time. Reproduce and identify the causal input; the variable should not affect the supplied Chapter 01 build model.
Why does a matching artifact checksum after two warm runs not prove full reproducibility?
Both runs can share the same JDK, dependency cache, repository metadata, OS, locale, workspace, and other hidden inputs. It is evidence within that controlled scope, not proof across independent environments.
A teammate proposes deleting ~/.m2 and
~/.gradle to make the checkpoint cleaner. What is
the safer method?
Use isolated per-lab Maven local repository and Gradle User Home paths. That produces fresh-state evidence without destroying unrelated developer state.
What is the strongest Chapter 01 handoff statement?
State exactly which inputs and versions were controlled, what tests/artifacts/checksums were verified, what changed under declared and unrelated environmental perturbations, and which production boundaries remain untested.
Summary
- You treated the build as evidence-producing automation, not a single success message.
- You recorded JDK, Maven wrapper, Gradle wrapper, source, model, graph, test, JAR-content, and SHA-256 evidence.
- You predicted and verified the effects of a clean build, a repeat build, a declared source change, and an unrelated environmental change.
- You kept dependency/cache state isolated and avoided destructive “delete all caches” troubleshooting.
- You documented the boundaries the local experiment did not prove, which is essential for honest production readiness.
This completes Chapter 01’s build-engineering mental model. The key operating principle is now explicit: before optimizing, publishing, or automating a build, be able to name its inputs, execution model, outputs, versions, trust boundaries, and evidence.
Primary sources and version notes
This checkpoint was authored against the official Maven/Gradle documentation available on 2026-08-23. Re-check current versions and compatibility before using these pins in a real project.
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.