Chapter 06Lesson 05~180 minutes

Checkpoint Lab — Maven Dependencies, Scopes, Transitive Resolution, Exclusions, Optional Dependencies, and Classpaths

Construct and verify a Maven graph with multiple scopes, an exclusion, and an optional edge; predict compile/test/runtime classpaths, inject a scope-related runtime failure, and repair coupling without destructive cache cleanup.

Checkpoint LabClasspath EvidenceScope FailureDependency GraphCleanup

Learning objectives

  • Construct a Maven graph containing compile, runtime, provided, and test scopes plus one exclusion and one optional edge.
  • Predict at least two graph/classpath changes before executing them, then verify with dependency tree and generated classpath evidence.
  • Prove a runtime-scope dependency appears at runtime without being required for main compilation.
  • Inject a provided-scope runtime failure, diagnose it from preserved evidence, and repair the deployment/model contract deliberately.
  • Reduce unnecessary coupling while preserving behavior and leave only disposable lab state behind.
Current baseline — verified 2026-08-23. The production teaching path uses Maven 3.9.16, Maven Wrapper 3.3.4, Maven Dependency Plugin 3.11.0, JDK 21, and Java 17 bytecode/API targeting. All labs use a disposable project directory plus -Dmaven.repo.local=<lab>/.lab-m2/repository; normal ~/.m2 state is never deleted. Network access is used only for immutable public dependencies. A warm-cache replay is optional; the conceptual path remains understandable from the supplied expected evidence.
Cross-platform note: command blocks labeled POSIX shell use ./mvnw, grep, find, and : classpath separators. On Windows use mvnw.cmd, PowerShell equivalents such as Select-String/Get-ChildItem, and ; as the Java classpath separator. The Maven model and scope semantics are the same.

1. Checkpoint scenario and acceptance contract

You are preparing scope-app for a production-style CI handoff. The build must explain every dependency edge and every runtime assumption. Reuse the producer/consumer model from Lesson 2 or recreate it from the supplied files. No hosted repository or paid service is required.

CHECKPOINT ACCEPTANCE
[ ] wrapper/Maven/JDK identities recorded
[ ] isolated local repository recorded
[ ] >= 4 ordinary scopes represented: compile, runtime, provided, test
[ ] >= 1 transitive exclusion represented
[ ] >= 1 optional producer edge represented
[ ] dependency tree captured
[ ] compile/runtime/test classpaths captured
[ ] two predictions written before changes
[ ] runtime-only H2 probe verified
[ ] one deliberate scope-related runtime failure captured
[ ] failure diagnosed from graph/classpath evidence
[ ] least-destructive repair verified
[ ] unnecessary coupling reviewed/removed
[ ] cleanup affects only disposable lab state

2. Preflight — record identities before the graph

Do not start by editing the POM. Record the execution environment and wrapper state first.

cd maven-scope-lab
REPO="$PWD/.lab-m2/repository"
DEP=org.apache.maven.plugins:maven-dependency-plugin:3.11.0
mkdir -p checkpoint-evidence

./mvnw --version | tee checkpoint-evidence/maven-version.txt
java -version 2> checkpoint-evidence/java-version.txt
cp .mvn/wrapper/maven-wrapper.properties checkpoint-evidence/wrapper.properties
sha256sum optional-feature-lib/pom.xml scope-app/pom.xml   > checkpoint-evidence/pom-input-sha256.txt
printf 'localRepository=%s
' "$REPO" > checkpoint-evidence/repository.txt

Assumptions: Maven 3.9.16, Wrapper 3.3.4, Dependency Plugin 3.11.0, JDK 21, Java release 17. If your wrapper or Maven runtime differs, stop and update the evidence/compatibility analysis rather than pretending the version does not matter.

3. Draw and predict the graph before resolving

The intended graph is:

Checkpoint dependency graph
flowchart LR
  A[scope-app]
  F[optional-feature-lib 1.0.0]
  C[commons-codec 1.17.1]
  T[commons-text 1.10.0]
  L1[commons-lang3 transitive request]
  L2[commons-lang3 3.17.0 direct]
  H[H2 2.3.232 runtime]
  S[Servlet API 6.1.0 provided]
  J[JUnit Jupiter 5.13.4 test]
  A -->|compile| F
  F -. optional .-> C
  A -->|compile| T
  T -. excluded .-> L1
  A -->|compile| L2
  A -->|runtime| H
  A -->|provided| S
  A -->|test| J
Prediction A:
  commons-codec will NOT appear in scope-app's resolved consumer tree,
  because optional-feature-lib marks that dependency optional.

Prediction B:
  jakarta.servlet-api will appear in compile/test classpath evidence,
  but NOT in the Maven runtime classpath.

Prediction C:
  H2 will appear in runtime/test classpath evidence but not compile.

Prediction D:
  commons-lang3 3.17.0 will be the intentional direct edge;
  the commons-text -> commons-lang3 transitive path is excluded.

At least two predictions are mandatory. Writing them first prevents post-hoc explanations that merely restate whatever Maven happened to print.

4. Install the producer into only the isolated repository

The optional producer must exist as an ordinary Maven coordinate so the consumer reads its installed POM metadata.

./mvnw -Dmaven.repo.local="$REPO" -f optional-feature-lib/pom.xml clean install   | tee checkpoint-evidence/producer-install.log

find "$REPO/dev/academy/optional-feature-lib/1.0.0" -maxdepth 1 -type f -print   | tee checkpoint-evidence/producer-installed-files.txt

grep -n -A5 -B3 '<optional>true</optional>'   "$REPO/dev/academy/optional-feature-lib/1.0.0/optional-feature-lib-1.0.0.pom"   | tee checkpoint-evidence/optional-metadata.txt

install is deliberately local. It proves consumer metadata behavior without remote repository credentials or mutable publishing infrastructure.

5. Build and capture dependency/classpath evidence

Run tests, capture the resolved tree, then generate classpath projections. Store evidence outside scope-app/target when you want it to survive clean.

./mvnw -Dmaven.repo.local="$REPO" -f scope-app/pom.xml clean test   | tee checkpoint-evidence/app-test.log

./mvnw -Dmaven.repo.local="$REPO" -f scope-app/pom.xml "$DEP":tree   -DoutputFile=target/dependency-tree.txt
cp scope-app/target/dependency-tree.txt checkpoint-evidence/dependency-tree.txt

for scope in compile runtime test; do
  ./mvnw -Dmaven.repo.local="$REPO" -f scope-app/pom.xml     "$DEP":build-classpath -DincludeScope="$scope"     -Dmdep.outputFile="target/${scope}.cp"
  cp "scope-app/target/${scope}.cp" "checkpoint-evidence/${scope}.cp"
done

# POSIX-only display helper:
for f in checkpoint-evidence/{compile,runtime,test}.cp; do
  echo "--- $f ---"
  tr ':' '
' < "$f"
done

6. Verify the predictions independently

Prediction Expected evidence Pass condition
Optional Codec does not transit Dependency tree has optional-feature-lib but no commons-codec. No consumer Codec node unless explicitly declared.
Servlet API provided Compile/test classpaths contain servlet JAR; runtime classpath does not. Classpath files match scope contract.
H2 runtime Runtime/test contain H2; compile does not. DbProbe loads org.h2.Driver with runtime CP.
Lang exclusion/replacement Tree shows direct Lang 3.17.0 and not the excluded Commons Text → Lang edge. Application has an explicit direct Lang contract.
grep -q 'optional-feature-lib' checkpoint-evidence/dependency-tree.txt
grep -q 'commons-codec' checkpoint-evidence/dependency-tree.txt   && echo 'FAIL: optional dependency unexpectedly propagated'   || echo 'PASS: optional dependency absent'

grep -q 'jakarta.servlet-api' checkpoint-evidence/compile.cp
grep -q 'jakarta.servlet-api' checkpoint-evidence/runtime.cp   && echo 'FAIL: provided dependency in runtime CP'   || echo 'PASS: provided dependency absent from runtime CP'

grep -q '/h2-' checkpoint-evidence/runtime.cp
grep -q '/h2-' checkpoint-evidence/compile.cp   && echo 'FAIL: runtime dependency in compile CP'   || echo 'PASS: H2 absent from compile CP' 

7. Verify normal and runtime-only behavior

Run the normal application and H2 runtime probe using only target/classes plus the generated runtime dependency classpath.

CP="scope-app/target/classes:$(cat checkpoint-evidence/runtime.cp)"
java -cp "$CP" dev.academy.App Academy   | tee checkpoint-evidence/app-runtime.txt
java -cp "$CP" dev.academy.DbProbe   | tee checkpoint-evidence/h2-runtime.txt
hello Academy / feature-core
h2-runtime-present

8. Inject one scope-related runtime failure

The application contains ContainerOnlyEndpoint, compiled against the Servlet API in provided scope. A plain JVM is intentionally not a servlet container. Try to load that class with the Maven runtime classpath and preserve the failure.

set +e
java -cp "$CP" dev.academy.ContainerOnlyEndpoint   > checkpoint-evidence/provided-failure.out   2> checkpoint-evidence/provided-failure.err
status=$?
set -e
printf 'exit=%s
' "$status" | tee checkpoint-evidence/provided-failure.status
sed -n '1,20p' checkpoint-evidence/provided-failure.err

# Confirm the missing provider is consistent with the runtime CP:
grep -q 'jakarta.servlet-api' checkpoint-evidence/runtime.cp   && echo 'unexpected: servlet API present'   || echo 'expected: servlet API absent' 

Diagnosis: this is not a broken Maven build. The class was compiled under a provided-dependency contract and then launched outside the provider environment. The correct repair depends on architecture: either run/test it inside the compatible servlet container that owns the API, or—if the component is actually standalone—change the application dependency/packaging model so it supplies the required runtime library.

9. Repair deliberately and verify the changed contract

For this training checkpoint, choose the architectural statement explicitly. Assume the component is intended to be standalone, not container-hosted. Change only the Servlet API dependency from provided to ordinary compile scope (remove the <scope>provided</scope> element). Then predict: the runtime classpath will gain jakarta.servlet-api; the dependency tree coordinate/version will remain; a plain JVM will get past the missing-superclass failure and then report that the class has no main method.

# Edit only scope-app/pom.xml: remove the servlet dependency's provided scope.
./mvnw -Dmaven.repo.local="$REPO" -f scope-app/pom.xml clean compile
./mvnw -Dmaven.repo.local="$REPO" -f scope-app/pom.xml "$DEP":build-classpath   -DincludeScope=runtime -Dmdep.outputFile=target/runtime-repaired.cp
cp scope-app/target/runtime-repaired.cp checkpoint-evidence/runtime-repaired.cp

grep 'jakarta.servlet-api' checkpoint-evidence/runtime-repaired.cp

CP2="scope-app/target/classes:$(cat checkpoint-evidence/runtime-repaired.cp)"
set +e
java -cp "$CP2" dev.academy.ContainerOnlyEndpoint   > checkpoint-evidence/repaired-load.out   2> checkpoint-evidence/repaired-load.err
status=$?
set -e
printf 'exit=%s
' "$status"
sed -n '1,12p' checkpoint-evidence/repaired-load.err

# Expected change: no NoClassDefFoundError for HttpServlet.
# A "main method not found" message is acceptable evidence that class loading passed.

In a real servlet application, the opposite repair could be correct: keep provided and fix the deployment/test environment. The checkpoint scores the reasoning and evidence, not a universal preference for compile scope.

10. Remove unnecessary coupling while preserving behavior

Review the graph rather than deleting random dependencies. Two examples:

  • If scope-app never uses DigestFeature, leave Commons Codec absent. Do not add it “just in case.”
  • If source no longer imports Commons Lang after refactoring to only Commons Text APIs, remove the direct Lang declaration and re-evaluate whether the exclusion is still necessary. If source still imports StringUtils, keep the direct dependency.

Every removal must be followed by clean test, dependency-tree comparison, runtime probes, and—where relevant—artifact/security evidence. A smaller graph is valuable only when behavior remains correct.

11. Verification checklist

Check Evidence
Execution identity maven-version.txt, java-version.txt, wrapper properties.
Input identity POM SHA-256 file.
Optional producer metadata Installed POM excerpt with optional=true.
Resolved graph dependency-tree.txt.
Classpath projections compile.cp, runtime.cp, test.cp.
Runtime dependency behavior h2-runtime.txt.
Failure evidence Provided-class original stderr/status.
Repair evidence Repaired runtime CP + changed class-loading error shape.
Cache boundary All Maven resolution under disposable .lab-m2.

12. Cleanup and rollback

Keep checkpoint-evidence only if you want the learning record. Cleanup must not touch normal Maven settings, wrapper distributions outside the lab, or ~/.m2/repository.

cd ..
pwd
ls -la maven-scope-lab

# Optional: copy maven-scope-lab/checkpoint-evidence somewhere safe.
rm -rf maven-scope-lab
Rollback boundary: no remote repositories, credentials, signing keys, global settings, or production caches were changed. If you deviated from the lab and added any external resource, revoke/remove it separately.

Knowledge check

Why is the checkpoint’s provided-scope failure useful even though the POM is not necessarily wrong?

After changing Servlet API from provided to compile, what two things should change and what should not?

Why does the checkpoint preserve predictions before running Maven?

Commons Codec is absent from the consumer tree. What proves this is intentional rather than a repository outage?

What is the production lesson from “remove unnecessary coupling”?

13. What Chapter 06 adds to a production build-engineering model

  • Dependency ownership: direct source/API requirements are declared directly.
  • Classpath contracts: compile, runtime, provided, and test visibility are independently inspectable.
  • Graph governance: exclusions are path-specific and optional edges are consumer-boundary decisions.
  • Deployment awareness: provided scope moves responsibility to an external runtime that must be tested.
  • Diagnostic discipline: model → tree → classpath → runtime evidence precedes corrections or cache experiments.
  • Supply-chain discipline: repository/cache availability is distinct from trust, vulnerability, and provenance.

Chapter 07 builds on this resolved model by examining Maven plugins, lifecycle bindings, plugin configuration/executions, and plugin management—the executable code that turns Maven phases into concrete work.

Official references and version notes

Version-sensitive statements in this lesson were checked against Apache Maven primary documentation on 2026-08-23. Required labs use Maven 3.9.16 through Maven Wrapper 3.3.4, Maven Dependency Plugin 3.11.0 for graph/classpath inspection, JDK 21 to run Maven, and Java 17 as the application release target. Maven 4.0.0-rc-6 remains preview-stage and is not required here.

Checkpoint scope projections use Maven Dependency Plugin 3.11.0. Maven’s dependency documentation is the authority for transitivity/scope behavior; plugin includeScope is used only to materialize classpath views.

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.