Chapter 02Lesson 05~120 minutes

Checkpoint Lab — Java and JVM Project Structure, Source Sets, Compilation, Testing, Packaging, and Toolchains

Prove the full JVM project contract by mapping source and resource inputs to compiled outputs, tests, bytecode target, JDK/toolchain identity, and packaged JAR contents, then diagnose a deliberate target mismatch.

Checkpoint LabVerificationToolchainsArtifactsRecovery

Learning objectives

  • Build a small JVM application with production code, tests, resources, and a production JAR using a controlled Maven or Gradle path.
  • Map each authored source/resource directory to generated output and prove what enters the artifact.
  • Record build-runtime JDK, selected toolchain where applicable, compiler release target, test evidence, and artifact checksum.
  • Predict and verify at least two changes to model/output state before making them.
  • Inject and recover from a deliberate Java release mismatch without touching production credentials or shared caches.
Version baseline — verified 2026-08-23. Labs use JDK 21 as the common build-runtime JDK, Apache Maven 3.9.16, Maven Compiler Plugin 3.15.0, Maven Toolchains Plugin 3.2.0 where toolchain discovery is demonstrated, Gradle 9.7.1, and JUnit Jupiter 5.13.4. Production bytecode is deliberately targeted to Java 17 with the compiler --release mechanism so the lesson can separate “JDK that runs the build” from “Java release the artifact targets.” Maven 4 preview behavior is not required in this chapter. Re-check current versions before reusing the examples in production.

1. Checkpoint scenario and acceptance contract

You are preparing a tiny JVM component for a CI team. They will not accept “it works on my machine.” Your handoff must prove the source/test/resource boundaries, JDK and build-tool identity, target Java release, executed tests, package contents, and artifact checksum. Use either Maven or Gradle as the primary track; complete the evidence-equivalent commands for the other tool if both wrappers are available.

[ ] production source and resources are in declared directories
[ ] test source/resources are separate from production output
[ ] build runtime JDK recorded
[ ] project toolchain selection/candidates recorded where supported
[ ] compiler release target = Java 17
[ ] tests executed (not merely discovered/skipped)
[ ] production JAR contains expected classes + app.properties
[ ] production JAR excludes GreeterTest.class
[ ] class-file major version recorded
[ ] SHA-256 of final JAR recorded
[ ] deliberate release mismatch failed for the expected reason
[ ] release target restored and clean verification passed
[ ] only disposable lab state removed during cleanup

2. Preflight and workspace

Use the project from Lesson 2 or recreate it in a fresh disposable directory. Record exact versions before changing anything.

pwd
git status --short 2>/dev/null || true
java -version
javac -version
./mvnw -v 2>/dev/null || true
./gradlew -version 2>/dev/null || true
Cleanup boundary: never run a recursive delete until pwd and the target path prove you are inside the disposable lab. This checkpoint does not require modifying ~/.m2, ~/.gradle, global toolchains, repository credentials, or IDE installations.

3. Predict the source/resource map before the build

Input Predicted Maven output Predicted Gradle output Production JAR?
src/main/java/.../Greeter.java target/classes/.../Greeter.class build/classes/java/main/.../Greeter.class yes
src/main/java/.../Main.java main classes main classes yes
src/main/resources/app.properties target/classes/app.properties build/resources/main/app.properties yes
src/test/java/.../GreeterTest.java target/test-classes build/classes/java/test no

Prediction 1: changing only app.properties should change processed resource/package bytes but should not require a semantic change to Greeter.class. Prediction 2: changing compiler release from 17 to 22 under JDK 21 should stop at compilation and produce no valid Java-22 class output.

4. Build, test, and package

Choose the command for your primary track. Do not combine generated directories from Maven and Gradle into one project during the checkpoint.

./mvnw clean test package
cat target/surefire-reports/*.txt
jar tf target/*.jar
./gradlew clean test jar
cat build/test-results/test/TEST-*.xml
jar tf build/libs/*.jar

Stop if the test count is zero, expected resources are missing, or a test class appears in the production JAR. A green process is not enough if the evidence violates the acceptance contract.

5. Prove compiler target independently

javap -verbose target/classes/academy/greeting/Greeter.class | grep "major version"
# Expected for Java 17: major version: 61
javap -verbose build/classes/java/main/academy/greeting/Greeter.class | grep "major version"
# Expected for Java 17: major version: 61

Record the result in your handoff. The compiler target is now evidenced by the class bytes, not inferred from a build-file comment.

6. Prove the build/runtime/toolchain identities

./mvnw -v
./mvnw org.apache.maven.plugins:maven-toolchains-plugin:3.2.0:display-discovered-jdk-toolchains
./gradlew -version
./gradlew javaToolchains

Label what each output proves. Maven/Gradle version output identifies the JVM running the build. Toolchain discovery/reporting identifies available/selected candidates. The class-file inspection proves the emitted target. Do not collapse those into one “Java version” field.

7. Record exact artifact identity and contents

# Maven
JAR=$(find target -maxdepth 1 -name '*.jar' -type f | head -n 1)
jar tf "$JAR" | sort
sha256sum "$JAR"

# Gradle (run in Gradle project)
JAR=$(find build/libs -maxdepth 1 -name '*.jar' -type f | head -n 1)
jar tf "$JAR" | sort
sha256sum "$JAR"
Get-FileHash .\target\*.jar -Algorithm SHA256
# or
Get-FileHash .\build\libs\*.jar -Algorithm SHA256

Require academy/greeting/Greeter.class, academy/greeting/Main.class, and app.properties. Require the absence of GreeterTest.class.

8. Verify Prediction 1 — change a declared resource input

Record the current JAR hash, change app.properties to name=BuildEngineer, rebuild, and record the new hash. Inspect the packaged resource. The artifact identity should change because a declared packaged input changed.

unzip -p target/*.jar app.properties 2>/dev/null || true
unzip -p build/libs/*.jar app.properties 2>/dev/null || true

Restore name=DevOps after the experiment and rebuild before the failure injection.

9. Verify Prediction 2 — deliberate toolchain/target mismatch

Temporarily request Java release 22 while the selected compiler is JDK 21. This is an intentional failure. Preserve the error text and do not “fix” it by installing unreviewed software.

<maven.compiler.release>22</maven.compiler.release>
options.release = 22
Compilation fails before a valid package is produced.
Causal message: requested Java release is not supported by the selected compiler.

Recovery:
1. restore release = 17
2. run a controlled clean build
3. verify tests, JAR contents, and class-file major version 61 again

10. Optional isolated-state verification

If you want stronger evidence that normal user cache state is not hiding the result, rerun using an isolated Maven local repository or Gradle User Home. This increases network work and is optional; it must not delete normal caches.

mkdir -p .checkpoint-m2/repository
./mvnw -Dmaven.repo.local="$PWD/.checkpoint-m2/repository" clean test package
mkdir -p .checkpoint-gradle-home
GRADLE_USER_HOME="$PWD/.checkpoint-gradle-home" ./gradlew clean test jar

11. Build the handoff record

Chapter 02 JVM structure checkpoint
Build tool + version: ____________________
Build runtime JVM:     ____________________
Toolchain/candidate:    ____________________
Compiler release:       17
Class major version:    61
Tests executed:         ____________________
Production JAR:         ____________________
SHA-256:                ____________________
Contains app.properties: YES / NO
Contains GreeterTest:    YES / NO
Mismatch injected:       release 22
Observed causal error:   ____________________
Recovery verified:       YES / NO

12. Cleanup and rollback

Restore all temporary edits first and run one final successful verification. Then delete only project-generated output and optional isolated lab state. Keep authored source/build files if you want to reuse the lab in Chapter 03.

pwd
# Inspect before deletion:
ls -ld target build .checkpoint-m2 .checkpoint-gradle-home 2>/dev/null || true

# Tool-owned generated output:
./mvnw clean 2>/dev/null || true
./gradlew clean 2>/dev/null || true

# Optional isolated state created only by this checkpoint:
# rm -rf .checkpoint-m2 .checkpoint-gradle-home

Knowledge check

Your wrapper runs on JDK 21 and the class-file major version is 61. Which Java deployment baseline does the inspected class represent?

The production JAR contains GreeterTest.class. Is that acceptable for this checkpoint?

Changing only app.properties changes the JAR checksum. Is that surprising?

The release-22 experiment fails under JDK 21. What is the correct recovery?

Why can a clean build still be insufficient proof of reproducibility?

What is the first question when CI and an IDE disagree?

Summary and production bridge

Chapter 02 adds a concrete JVM compatibility contract to the build-engineering model: production/test source boundaries, classpaths, generated outputs, test evidence, toolchain identity, compiler release target, package contents, and artifact checksum. You can now explain exactly which JDK compiles the code, which Java release the bytes target, and which files enter the artifact.

Chapter 03 builds on this foundation by adding external coordinates, repositories, metadata, transitive dependency graphs, and version selection. Those mechanisms are easier to reason about once you know which classpath and consumer each resolved dependency feeds.

Official references and version notes

These lessons were finalized against current primary documentation on 2026-08-23. Build-tool, plugin, JDK, repository, and IDE behavior is version-sensitive; verify the exact versions used by your project and CI before applying a production policy.

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.