Chapter 05Lesson 05~165 minutes

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.

Checkpoint LabLifecycle EvidenceArtifact IdentityLocal RepositoryTroubleshooting

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.
Current baseline — checked 2026-08-23. Required labs use Maven 3.9.16 through Maven Wrapper 3.3.4, JDK 21 to run Maven, and Java 17 as the compilation release target. For Maven 3.9.16 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
Stop if identity is unexpected. Do not silently edit wrapper distribution URLs/checksums or global settings to force the lab forward. Reconcile the environment first.

3. Predict before executing

Write predictions into target/evidence/predictions.md before the first package build. At minimum predict these three changes:

  1. compile will create classes and copy main resources under target/classes, but will not create the project JAR.
  2. package will create target/lifecycle-lab-1.0.0.jar and include build.properties.
  3. install will 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
Rollback boundary: this checkpoint never needed remote deployment, credentials, signing keys, or changes to global Maven settings. If your environment introduced those, remove/revoke them separately and document that deviation.

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?

Why is prepare-package a better repair than invoking jar:jar a second time?

After verify, the isolated repository has no dev/academy/lifecycle-lab/1.0.0 directory. Is that a failure?

A plugin appears in the lifecycle log but not in the child POM. What two places should you inspect?

What evidence proves a build artifact, rather than merely proving that Maven reported success?

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: install is local; deploy is 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.