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.
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.
-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.
./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:
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-appnever usesDigestFeature, 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
Knowledge check
Why is the checkpoint’s provided-scope failure useful even though the POM is not necessarily wrong?
It proves that scope is a deployment contract. A plain JVM does not satisfy the external-provider assumption, so diagnosis must distinguish model correctness from environment correctness.
After changing Servlet API from provided to compile, what two things should change and what should not?
The runtime classpath should gain the Servlet API and the missing-superclass error should disappear. The selected coordinate/version should not change merely because the scope changed.
Why does the checkpoint preserve predictions before running Maven?
Predictions make the exercise causal: you can test whether the graph/classpath behaves as the model implies instead of rationalizing output after the fact.
Commons Codec is absent from the consumer tree. What proves this is intentional rather than a repository outage?
The producer’s installed POM contains
optional=true, the producer itself successfully
built with Codec, and the consumer tree omits Codec according to
Maven optional propagation semantics.
What is the production lesson from “remove unnecessary coupling”?
Minimize dependencies only when source/runtime behavior and deployment contracts remain correct. Smaller classpaths improve clarity and may reduce risk, but arbitrary deletion creates failures.
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.
- Maven — Introduction to the Dependency Mechanism
- Maven — Optional Dependencies and Dependency Exclusions
- Maven — Dependency and Repository Model
- Maven — POM Reference
- Maven Dependency Plugin 3.11.0 — Usage
- Maven Dependency Plugin 3.11.0 — Plugin Details
- Maven Dependency Plugin — dependency:tree
- Maven Dependency Plugin — dependency:build-classpath
- Maven Releases History
- Apache Maven Wrapper
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.