What is a Maven Lifecycle?
A lifecycle is a fixed, ordered sequence of named
phases. Phases don't do anything by themselves — they're
just labelled checkpoints. What actually happens during a phase is whatever
plugin goals are bound to it. Maven ships three independent
lifecycles: default (build and deploy), clean
(remove build output), and site (generate documentation).
The problem this solves: every Java project needs roughly the same sequence
of steps — compile, test, package — but different projects need different
tools to do each step. The lifecycle gives every project the same
named checkpoints; plugins decide what actually runs at each one. This is why
mvn test means the same conceptual thing whether your project
uses JUnit 4, JUnit 5, or TestNG — the phase is universal, the tool behind it
isn't.
The default Lifecycle — Full Phase List
23 phases in fixed order. Running any phase executes every phase before it automatically. In practice you'll name maybe 6 of these directly; the rest exist as hooks for plugins to bind custom goals to.
validate # project structure is correct
initialize # set up build state, e.g. create directories
generate-sources # generate any source code to be compiled (codegen)
process-sources # e.g. filter/template source files
generate-resources # generate resources for inclusion in the package
process-resources # copy resources to target/classes
compile # ★ compile src/main/java → target/classes
process-classes # post-process compiled bytecode (e.g. bytecode enhancement)
generate-test-sources # generate test source code
process-test-sources # process test source files
generate-test-resources # generate test resources
process-test-resources # copy test resources
test-compile # compile src/test/java
process-test-classes # post-process compiled test classes
test # ★ run unit tests (Surefire)
prepare-package # pre-packaging hook (e.g. Spring Boot repackage groundwork)
package # ★ bundle into jar/war in target/
pre-integration-test # set up environment for integration tests (start containers, etc.)
integration-test # run integration tests (Failsafe)
post-integration-test # tear down integration test environment
verify # ★ run checks to validate the package is correct
install # ★ copy artifact to ~/.m2/repository
deploy # ★ upload artifact to a remote repository
★ marks the phases you'll actually type as commands daily — everything else is invoked implicitly because it runs before whichever ★ phase you called.
Phases like generate-sources or
process-test-classes are rarely named directly — they exist
so plugins have a guaranteed, well-defined slot to hook into without
clashing with each other. Code generators (Lombok's annotation
processing, Protobuf compilation, OpenAPI client generation) bind to
generate-sources specifically so their output is ready
before the real compile phase runs.
The clean and site Lifecycles
These are entirely separate from default — they don't share
phases, and running one doesn't trigger the other (which is why
mvn clean install explicitly names both).
// clean lifecycle — 3 phases
pre-clean // hook before cleaning
clean // ★ deletes target/
post-clean // hook after cleaning
// site lifecycle — 4 phases, generates project documentation/reports
pre-site
site // ★ generates HTML docs into target/site/
post-site
site-deploy // uploads the generated site somewhere
mvn clean package // runs: pre-clean → clean → post-clean, THEN validate → ... → package
mvn site:run // serves the generated site locally for preview
Binding Goals to Phases
A goal is a single unit of work a plugin can perform — e.g.
compiler:compile, surefire:test. Each phase has
default goal bindings for standard packaging types, and you
can bind additional goals to any phase explicitly.
Default bindings for jar packaging
process-resources → resources:resources
compile → compiler:compile
process-test-resources → resources:testResources
test-compile → compiler:testCompile
test → surefire:test
package → jar:jar
install → install:install
deploy → deploy:deploy
// This is WHY 'mvn test' runs Surefire without you configuring anything —
// the binding already exists for jar packaging by default.
Binding a custom goal — example: enforce code style before anything builds
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase> <!-- runs FIRST, fails fast before compiling -->
<goals><goal>check</goal></goals>
</execution>
</executions>
</plugin>
// Run a goal directly, bypassing the lifecycle entirely (no preceding phases)
// — useful for one-off checks, not for a real build
mvn checkstyle:check
mvn compiler:compile // compiles WITHOUT running validate/initialize first
mvn package invokes a phase — every phase before it
runs too. mvn compiler:compile invokes a goal
directly — only that goal runs, nothing else. Use phase invocation for
actual builds; use direct goal invocation only for quick, isolated
checks where you deliberately don't want the full lifecycle overhead.
Skipping Phases and Plugin Executions
# Test-related skips — different scope, choose carefully
mvn package -DskipTests # compiles tests, doesn't RUN them
mvn package -Dmaven.test.skip=true # doesn't even compile them — faster, less safe
# Skip a specific quality-gate plugin without disabling tests
mvn package -Dcheckstyle.skip=true
mvn package -Dspotbugs.skip=true
# Skip in the POM itself — avoid this for CI; it silently disables
# the check for EVERYONE, not just your local run
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration><skip>true</skip></configuration>
</plugin>
Interview Questions
Q: What happens when you run mvn package?
Maven executes every phase of the default lifecycle in order,
up to and including package: validate, compile, test, package.
It doesn't skip ahead — phases always run in their fixed sequence.
Q: What is the difference between a phase and a goal?
A phase is a named checkpoint in the lifecycle (e.g. compile) —
it does nothing by itself. A goal is the actual unit of work a plugin
performs (e.g. compiler:compile). Phases have goals bound to
them; running a phase executes whatever goals are bound to it and every
phase before it.
Q: Are clean, default, and site related?
No — they're three completely independent lifecycles that don't share
phases. Running mvn clean only triggers the
pre-clean → clean → post-clean sequence; it has no effect on
default phases unless you explicitly chain them, like
mvn clean install.
Q: Why do code generators (Lombok, Protobuf, OpenAPI generators)
typically bind to generate-sources rather than some other phase?
generate-sources runs before compile but after
initialize — it's specifically designed as the phase where
source code that doesn't exist yet gets created, guaranteeing it's on disk
and ready before the compiler runs. Binding a generator any later (e.g. to
process-sources or beyond) risks generated classes not being
visible to the compiler in time; binding earlier risks build state not being
initialised yet.
Q: When would you invoke a goal directly instead of a phase?
When you want exactly one unit of work with no lifecycle overhead — for
example, mvn dependency:tree to inspect dependencies without
triggering a full compile, or mvn checkstyle:check as a quick
local style check during development. Direct goal invocation is for
diagnostics and quick checks; real builds should always go through phase
invocation so the full, correctly-ordered lifecycle runs.
Q: How would you make a build fail fast on a code-quality
violation before wasting time compiling and testing?
Bind the quality plugin's check goal to an early phase —
validate or initialize — rather than its default
binding (which for many quality plugins is later, around
verify). Failing at validate means a broken style
rule is caught in seconds, before the much more expensive compile and test
phases run at all. This is a real CI optimisation: cheap checks first,
expensive checks last.