Maven POM Structure, Coordinates, Packaging, Build Lifecycle, Phases, and Goals: Diagnostics, Failure Modes, Security, and Performance
Diagnose lifecycle and POM failures by preserving evidence, inspecting the effective model and bindings, and repairing phase/goal, packaging, inheritance, install, and duplicate-execution mistakes safely.
Learning objectives
- Apply a repeatable evidence-first diagnostic sequence to Maven lifecycle and POM failures.
- Recognize phase/goal confusion and duplicate/default plugin execution from build logs.
- Diagnose packaging and inherited-effective-POM mismatches before editing plugins or deleting caches.
-
Prove that
installis local and distinct from remotedeploy. - Repair a deliberately misplaced harmless goal by reasoning about phase ordering.
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. Diagnostic sequence — preserve causality first
# 1. Preserve concise identity and failure output.
./mvnw --version
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" package \
| tee target/failure.log
# 2. Inspect the declared/effective model.
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" \
org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom \
-Dverbose -Doutput=target/effective-pom.xml
# 3. Extract executed goals; do not erase the original log.
grep -E '^\[INFO\] --- .*:[0-9].*:.* \(' target/failure.log || true
# 4. Inspect target and the isolated repository only after the model.
find target -maxdepth 3 -type f -print | sort
find .lab-m2/repository -maxdepth 6 -type f -print | sort | head -100
The order matters. If you delete caches or
target before preserving evidence, you can remove the
state that explains the failure. Correct the smallest causal
configuration, then rerun in the same controlled environment.
2. Failure: a goal is confused with a phase
Symptom: an operator runs
compiler:compile expecting the same preparation as
compile. Java classes appear, but copied resources do
not.
Interpretation: compile is a
default-lifecycle phase. compiler:compile is one goal.
Direct goal invocation does not imply
process-resources.
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" clean
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" \
org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile
test -f target/classes/build.properties || echo "resource missing"
# Least-destructive correction: invoke the lifecycle contract you actually need.
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" compile
test -f target/classes/build.properties && echo "resource restored"
3. Failure: a goal runs twice because a default binding and explicit execution overlap
Symptom: the log contains both
(default-resources) and a second custom resources
execution in process-resources. The build may still
succeed, but it performs duplicate work and can apply
filtering/configuration twice.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>duplicate-resources</id>
<phase>process-resources</phase>
<goals><goal>resources</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
For jar packaging, Maven 3.9.16 already binds
resources:resources to process-resources.
If the second execution adds no intentional behavior, remove it. If
you need configuration, configure the plugin/default execution
instead of creating an accidental duplicate.
4. Failure: packaging does not match the expected artifact
Symptom: package succeeds, but no JAR
exists. The declared POM says
<packaging>pom</packaging>.
Interpretation: for pom packaging,
Maven core does not bind a normal artifact-producing goal at
package; install/deploy operate on POM metadata. The
build is behaving according to its model.
grep -n '<packaging>' pom.xml || echo "packaging omitted -> defaults to jar"
./mvnw org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom \
-Doutput=target/effective-pom.xml
grep -n '<packaging>' target/effective-pom.xml | head
Repair the architecture, not the symptom. Change packaging to
jar only if the project is genuinely a JAR-producing
module; do not bolt jar:jar onto a parent/aggregator
merely to satisfy an incorrect CI file expectation.
5. Failure: the effective POM contains inherited behavior you did not expect
Symptom: the child POM does not show a compiler release or plugin version that appears in the build log. A parent POM provides it.
Run help:effective-pom -Dverbose. The verbose output
annotates XML elements with origin information, allowing you to
distinguish child declarations, parent inheritance, profile
activation, and defaults. Do not “fix” the child by duplicating
inherited configuration until you know which layer owns policy.
./mvnw org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom \
-Dverbose -Doutput=target/effective-pom.xml
grep -n -B2 -A5 -E 'maven-compiler-plugin|<release>|<finalName>' \
target/effective-pom.xml | head -100
6. Failure: install is mistaken for remote publication
Symptom: a runbook claims “mvn install
published version 1.0.0,” but no remote repository contains it.
Interpretation: the Install Plugin writes artifacts
into the local repository using coordinates. Remote
publication belongs to the later deploy phase and
requires repository configuration/credentials. This lesson does not
require deployment.
REPO="$PWD/.lab-m2/repository"
rm -rf "$REPO/dev/academy/lifecycle-lab"
./mvnw -Dmaven.repo.local="$REPO" install
find "$REPO/dev/academy/lifecycle-lab/1.0.0" -maxdepth 1 -type f -print | sort
pom.xml merely to test deployment.
Publishing/signing are covered later with fake/disposable targets
and proper secret boundaries.
7. Intentionally broken example — harmless goal bound too late
Suppose the requirement is: “generate
build-marker.txt into target/classes
before the JAR is assembled, so the marker must be inside
the JAR.” The following execution is bound to the wrong phase:
<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>
For a JAR project, the packaging-provided jar:jar goal
is already bound to package. Maven’s lifecycle
documentation states that packaging bindings execute before
POM-configured goals in the same phase. Therefore the JAR can be
built before this custom antrun:run creates
the marker.
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" clean package \
| tee target/package-broken.log
jar tf target/lifecycle-lab-1.0.0.jar | grep build-marker \
|| echo "marker absent from jar"
grep -E '^\[INFO\] --- .*:(jar|run) ' target/package-broken.log
Least-destructive correction: move the custom
generation to prepare-package, an earlier phase
intended for work needed before packaging.
<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>
./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository" clean package
jar tf target/lifecycle-lab-1.0.0.jar | grep build-marker.txt
The repair changes ordering at the model layer. It does not delete caches, disable tests, or invoke the JAR plugin twice. The original failed evidence remains available for comparison.
8. Performance without cargo-cult cache deletion
Separate timing domains: dependency/plugin download (cold
repository), Maven model/configuration, resource processing,
compilation/tests, and packaging. A warm isolated repository should
reduce resolution time, but it should not change the effective POM
or artifact semantics for immutable inputs. If a local repository
entry is suspected, reproduce with a second
new isolated lab repository before deleting
anything from ~/.m2.
time ./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository-a" clean verify
time ./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository-a" clean verify
# Independent clean-room repository, not ~/.m2 deletion.
time ./mvnw -Dmaven.repo.local="$PWD/.lab-m2/repository-b" clean verify
Knowledge check
The log shows
resources:resources (default-resources) and
resources:resources (duplicate-resources). What
should you inspect first?
The effective POM and explicit plugin executions. JAR packaging already supplies a default resources binding, so a second execution may be redundant or intentionally customized.
A pom-packaging project produces no JAR at
package. Is the JAR plugin necessarily
broken?
No. pom packaging does not use the normal JAR
package binding. Verify the project’s intended packaging model.
A custom goal must generate a file that the JAR contains, but
it is bound to the same package phase as
jar:jar. What is the safer design?
Bind generation to an earlier appropriate phase such as
prepare-package, then verify the file exists before
the package goal runs.
Why is deleting ~/.m2/repository a poor first
diagnostic step?
It destroys shared cache evidence, downloads unrelated artifacts again, and may hide the causal model problem. Reproduce with an isolated disposable repository first.
After install, where should you first look for the
new project artifact in this lab?
The isolated local repository path, not a remote repository.
Remote publication is a deploy concern.
Summary
Maven lifecycle incidents are model/order incidents until evidence proves otherwise. Preserve the log, verify Maven/JDK identity, inspect declared and effective POM state, read the executed goal sequence, then inspect generated/local-repository files. Repair the smallest causal binding, packaging, inheritance, or phase choice. Avoid destructive cache rituals and never confuse local install with remote deployment.
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.
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.