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.
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.
--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
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?
Java 17. The wrapper/build runtime JVM and emitted class-file target are separate pieces of evidence.
The production JAR contains GreeterTest.class. Is
that acceptable for this checkpoint?
No. It violates the intended production/test packaging boundary and must be diagnosed before handoff.
Changing only app.properties changes the JAR
checksum. Is that surprising?
No. The resource is a declared packaged input, so changing its bytes should change the artifact identity.
The release-22 experiment fails under JDK 21. What is the correct recovery?
Restore the declared release to 17, rebuild cleanly, and independently verify tests, JAR contents, and class major version. Do not bypass the compiler check.
Why can a clean build still be insufficient proof of reproducibility?
It may reuse dependency/plugin caches and the same machine/toolchain/environment. Stronger evidence controls those inputs or repeats in isolated state.
What is the first question when CI and an IDE disagree?
Which build/toolchain/runtime/configuration is each one actually using? Compare identities and effective build state before changing source or dependencies.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.