Checkpoint Lab — Maven POM Structure, Coordinates, Packaging, Build Lifecycle, Phases, and Goals
Prove Maven lifecycle behavior end to end by tracing the effective POM, plugin-goal execution, package/install side effects, and a deliberately misplaced harmless goal.
Learning objectives
- Create a complete evidence package for one Maven JAR build from declared POM through local installation.
- Predict and verify phase, goal, artifact, and local-repository changes rather than treating Maven as a black box.
- Capture effective-POM and concise lifecycle-plan evidence without flooding the console with debug logs.
- Diagnose and repair a deliberately misplaced harmless plugin execution from ordering evidence.
- State the production build-engineering controls this chapter adds and hand off cleanly to Maven dependency scopes.
jar packaging, core default bindings currently
include Resources 3.4.0, Compiler 3.15.0, Surefire 3.5.4, JAR 3.5.0,
Install 3.1.4, and Deploy 3.1.4. The lab uses a disposable
project-local repository path; no remote publication, paid repository
manager, or hosted CI is required.
1. Checkpoint scenario and acceptance contract
You are preparing dev.academy:lifecycle-lab:1.0.0 for a
CI handoff. Another engineer must be able to answer: which Maven/JDK
executed, what the effective model contained, which goals ran
through each lifecycle boundary, what bytes were packaged, what
changed at install, and why a deliberately misplaced
generation goal failed to enter the JAR.
CHECKPOINT ACCEPTANCE
[ ] Maven Wrapper selects Maven 3.9.16; wrapper files/checksum were verified in Chapter 04
[ ] Maven runtime JDK identity recorded (JDK 21 baseline)
[ ] Java compilation release target recorded (17 baseline)
[ ] declared POM + input SHA-256 recorded
[ ] effective POM captured with origin comments
[ ] package goal sequence captured from normal Maven output
[ ] JAR contents + SHA-256 recorded
[ ] package vs install side effects independently compared
[ ] wrong-phase marker failure reproduced without hiding its log
[ ] marker binding moved to prepare-package and verified inside the JAR
[ ] isolated local repository used; normal ~/.m2 not deleted
[ ] cleanup reviewed and limited to the disposable lab
2. Setup and preflight
Use the project from Lesson 2 or recreate it from the same
pom.xml, App.java,
build.properties, and AppTest.java. Keep
the wrapper baseline from Chapter 04. This lab does not publish
remotely and requires no credentials.
cd lifecycle-lab
mkdir -p target/evidence .lab-m2/repository
REPO="$PWD/.lab-m2/repository"
MVN="./mvnw -Dmaven.repo.local=$REPO"
$MVN --version | tee target/evidence/maven-version.txt
java -version 2>&1 | tee target/evidence/java-version.txt
printf '%s\n' 'Expected Maven: 3.9.16 via Maven Wrapper 3.3.4' 'Expected build JVM: JDK 21 baseline' 'Expected compiler release: 17' > target/evidence/assumptions.txt
3. Predict before executing
Write predictions into
target/evidence/predictions.md before the first package
build. At minimum predict these three changes:
-
compilewill create classes and copy main resources undertarget/classes, but will not create the project JAR. -
packagewill createtarget/lifecycle-lab-1.0.0.jarand includebuild.properties. -
installwill add the POM/JAR under the coordinate-derived path in the isolated local repository; it will not remotely publish anything.
cat > target/evidence/predictions.md <<'EOF'
# Predictions before execution
1. compile -> target/classes/App.class + build.properties; no main JAR.
2. package -> target/lifecycle-lab-1.0.0.jar; resource included.
3. install -> isolated local repository receives POM/JAR; no remote publication.
4. broken package-phase marker -> file created too late to enter the JAR.
EOF
4. Capture declared and effective model evidence
The source POM is the declared model; the effective POM is execution evidence after Maven adds inherited/default state. Preserve both identities.
sha256sum pom.xml src/main/java/dev/academy/App.java \
src/main/resources/build.properties src/test/java/dev/academy/AppTest.java \
| tee target/evidence/input-sha256.txt
$MVN org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom \
-Dverbose -Doutput=target/evidence/effective-pom.xml
grep -n -E '<packaging>|<sourceDirectory>|<outputDirectory>|maven-(resources|compiler|surefire|jar|install)-plugin' \
target/evidence/effective-pom.xml | head -120 \
| tee target/evidence/effective-model-summary.txt
5. Trace validate through package with concise plan evidence
Use normal Maven output rather than an unbounded console
-X dump. The plugin execution banners provide a concise
execution plan.
$MVN clean
$MVN validate | tee target/evidence/validate.log
$MVN compile | tee target/evidence/compile.log
$MVN test | tee target/evidence/test.log
$MVN package | tee target/evidence/package.log
for f in validate compile test package; do
echo "=== $f ==="
grep -E '^\[INFO\] --- .*:[0-9].*:.* \(' "target/evidence/$f.log" || true
done | tee target/evidence/lifecycle-plan.txt
jar tf target/lifecycle-lab-1.0.0.jar | sort \
| tee target/evidence/jar-contents.txt
sha256sum target/lifecycle-lab-1.0.0.jar \
| tee target/evidence/jar-sha256.txt
Explain every observed goal by one of two sources: a packaging-provided default binding or an explicit POM execution. If you cannot explain a goal, return to the effective POM and matching Maven core binding reference before proceeding.
6. Compare package, verify, and
install side effects
First prove that packaging alone does not install the project into
the isolated repository. Then run verify and finally
install.
rm -rf "$REPO/dev/academy/lifecycle-lab"
$MVN clean package
test -f target/lifecycle-lab-1.0.0.jar && echo "package artifact: present"
test -d "$REPO/dev/academy/lifecycle-lab" || echo "local install: absent after package"
$MVN verify | tee target/evidence/verify.log
test -d "$REPO/dev/academy/lifecycle-lab" || echo "local install: still absent after verify"
$MVN install | tee target/evidence/install.log
find "$REPO/dev/academy/lifecycle-lab/1.0.0" -maxdepth 1 -type f -print | sort \
| tee target/evidence/install-files.txt
The JAR produced by the package/verify path remains a build output
under target. Install adds a coordinate-addressed copy
and POM metadata to the local repository. A remote repository is
still untouched.
7. Failure drill — bind a harmless goal to the wrong phase
Add the following explicit execution to the POM’s
<build><plugins>. The requirement is for
the marker to exist before the JAR is assembled, but it is
intentionally bound to package.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>write-build-marker</id>
<!-- INTENTIONALLY WRONG FOR THIS DEMO -->
<phase>package</phase>
<goals><goal>run</goal></goals>
<configuration>
<target>
<echo file="${project.build.outputDirectory}/build-marker.txt">marker-before-jar</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
$MVN clean package | tee target/evidence/broken-package.log
jar tf target/lifecycle-lab-1.0.0.jar | grep build-marker \
|| echo "EXPECTED FAILURE: marker absent from jar" \
| tee target/evidence/broken-observation.txt
grep -E '^\[INFO\] --- .*:(jar|run) ' target/evidence/broken-package.log \
| tee target/evidence/broken-order.txt
Interpretation: the default
jar:jar packaging binding executes at
package before POM-configured goals in that same phase.
The marker is generated after the JAR already exists. The failure is
ordering, not cache corruption.
8. Repair with the least destructive model change
Change only the execution phase from package to
prepare-package. Keep the same plugin, goal, file, and
isolated repository so the variable under test is phase ordering.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>write-build-marker</id>
<!-- Correct: marker is generated before the package phase -->
<phase>prepare-package</phase>
<goals><goal>run</goal></goals>
<configuration>
<target>
<echo file="${project.build.outputDirectory}/build-marker.txt">marker-before-jar</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
$MVN clean package | tee target/evidence/repaired-package.log
jar tf target/lifecycle-lab-1.0.0.jar | grep 'build-marker.txt' \
| tee target/evidence/repaired-marker.txt
# Record ordering evidence as well.
grep -E '^\[INFO\] --- .*:(run|jar) ' target/evidence/repaired-package.log \
| tee target/evidence/repaired-order.txt
The repair is complete only when both ordering evidence and artifact
inspection agree: the generation goal executes before
jar:jar, and the JAR contains the marker.
9. Verification checklist
| Check | Independent evidence |
|---|---|
| Maven/JDK identity |
target/evidence/maven-version.txt and
java-version.txt.
|
| Declared inputs | input-sha256.txt. |
| Effective model | effective-pom.xml + summary. |
| Executed lifecycle goals | lifecycle-plan.txt and per-phase logs. |
| Artifact contents/identity |
jar-contents.txt + jar-sha256.txt.
|
| Package/install distinction | Absence/presence of isolated coordinate path before/after install. |
| Failure diagnosis |
broken-order.txt + marker absent from broken
JAR.
|
| Repair |
repaired-order.txt + marker present inside
repaired JAR.
|
10. Cleanup and rollback
Before cleanup, optionally archive
target/evidence outside the lab if you want to retain
the training record. Then remove only the disposable project. Do not
delete normal Maven settings or the normal user repository.
cd ..
pwd
ls -la lifecycle-lab
# Optional: copy lifecycle-lab/target/evidence somewhere safe first.
rm -rf lifecycle-lab
Knowledge check
The package log shows jar:jar before
your custom antrun:run, and the marker is absent
from the JAR. What is the root cause?
Both goals are in the package phase, but the
packaging-provided default goal executes before POM-configured
goals in that phase. The generation goal is too late for a file
that must be packaged.
Why is prepare-package a better repair than
invoking jar:jar a second time?
It expresses the required ordering in the lifecycle model: generate the input before packaging. Running the packaging goal twice adds duplicate work and obscures causality.
After verify, the isolated repository has no
dev/academy/lifecycle-lab/1.0.0 directory. Is that
a failure?
No. The local install side effect belongs to the later
install phase.
A plugin appears in the lifecycle log but not in the child POM. What two places should you inspect?
The effective POM (including parent/profile inheritance) and the Maven runtime’s packaging-specific default lifecycle bindings.
What evidence proves a build artifact, rather than merely proving that Maven reported success?
Inspect the artifact itself—e.g., JAR contents—and record an identity such as SHA-256, alongside model and execution evidence.
11. What Chapter 05 adds to a production build-engineering model
- Model provenance: declared POM versus effective POM and inherited/default origins.
- Execution provenance: lifecycle phase request versus the ordered plugin goals that actually ran.
- Artifact provenance: packaging mapping, JAR contents, checksum, and install path.
- Failure discipline: preserve evidence, reason about bindings/order, make the least destructive correction, verify independently.
-
Scope discipline:
installis local;deployis remote; packaging and phase selection must match the intended outcome.
Chapter 06 now zooms into Maven dependencies: scopes, transitivity, exclusions, optional dependencies, and the compile/test/runtime classpaths those declarations create.
Official references and version notes
Version-sensitive statements in this lesson were checked against
Apache Maven primary documentation on 2026-08-23. The production
teaching baseline is Maven 3.9.16 through Maven Wrapper 3.3.4,
running on JDK 21 and compiling the lab project with
maven.compiler.release=17. Maven 4 is intentionally
outside this chapter’s required path because it remains
preview-stage. Default lifecycle plugin versions are properties of
the Maven runtime and can change when the Maven runtime changes;
re-check the matching core reference after an upgrade.
The wrong-phase lab also relies on Maven’s documented rule that packaging-provided goals execute before goals configured in the POM when both are bound to the same phase.
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.