Maven Build Lifecycle: Phases, Plugin Goals and Build Commands
The command mvn clean jar:jar ends with BUILD SUCCESS, and shop-1.0-SNAPSHOT.jar appears in the target directory. The only problem: there are no classes inside, and the log contains the warning JAR will be empty - no content was marked for inclusion!. The reason is that jar:jar is a plugin goal, not a phase: Maven ran exactly that goal and never compiled the code. mvn clean package would have produced a working jar, because the package phase drags all the earlier build steps along with it.
The Maven build lifecycle is a predefined, ordered sequence of phases a project goes through: validation, compilation, testing, packaging, installation and deployment. Maven has three built-in lifecycles: default (the build itself), clean (cleanup) and site (the project site). When you run a phase, for example mvn package, Maven executes every phase of that lifecycle up to and including it, and the actual work in each phase is done by the plugin goals bound to it.
Three Built-in Maven Lifecycles: default, clean, site
Maven has three independent built-in lifecycles: default builds the project from POM validation to artifact deployment, clean removes the output of the previous build, and site generates a project site with reports. A phase of one lifecycle never triggers phases of another: mvn install does not clean target, and mvn clean compiles nothing.
| Lifecycle | Phases (in order) | What it does | Typical command |
|---|---|---|---|
default | 23 phases from validate to deploy (see below) | Compiles, tests, packages, installs into the local repository, deploys | mvn package, mvn verify, mvn install |
clean | pre-clean, clean, post-clean | Deletes the build directory (target) with the clean:clean goal | mvn clean |
site | pre-site, site, post-site, site-deploy | Generates an HTML project site with reports (Javadoc, tests, code analysis) in target/site; site-deploy publishes it to a server | mvn site |
The word clean names both a lifecycle and its main phase, which is where the confusion in older articles comes from, where clean and site are described as "standalone phases". More precisely, they are separate lifecycles that run only when one of their phases is named on the command line. mvn site uses maven-site-plugin, and the reports that go into the site are configured in the <reporting> section of pom.xml.
You can list phases of different lifecycles in one command, and Maven runs them from left to right:
mvn clean package site First the clean lifecycle runs up to the clean phase, then default up to package, then site up to site: the project is built from scratch and gets a documentation site.
Maven Lifecycle Phases: from validate to deploy
The default lifecycle consists of 23 phases, but day-to-day commands use seven main ones: validate, compile, test, package, verify, install and deploy. The remaining phases are intermediate hook points where plugins attach preparation steps: generating code, copying resources, starting an environment for integration tests.
| Phase | What happens | Result |
|---|---|---|
validate | Checks that the POM is correct and all required information is present | An error if pom.xml lacks required elements |
compile | Compiles the main code (resources are copied right before it) | .class files in target/classes |
test | Compiles and runs unit tests with maven-surefire-plugin | Reports in target/surefire-reports; a failing test stops the build |
package | Packages the compiled code in the format set by <packaging> | target/shop-1.0-SNAPSHOT.jar (or .war) |
verify | Quality checks and checking integration test results (if plugins are bound to the phase) | The build fails if a check does not pass |
install | Copies the artifact and its POM into the local repository ~/.m2/repository | Other projects on this machine can use it as a dependency |
deploy | Publishes the artifact to the remote repository from <distributionManagement> | The artifact becomes available to the team and CI servers |
The full order of the default phases (Maven 3); the main ones are listed in the table above:
validate -> initialize
-> generate-sources -> process-sources -> generate-resources -> process-resources
-> compile -> process-classes
-> generate-test-sources -> process-test-sources -> generate-test-resources -> process-test-resources
-> test-compile -> process-test-classes -> test
-> prepare-package -> package
-> pre-integration-test -> integration-test -> post-integration-test
-> verify -> install -> deploy Phases with the pre-, post- and process- prefixes are almost never called directly: they exist so that a plugin can plug into the right point of the build. A phase with no goals bound to it does nothing: for a plain jar project, mvn validate only reads and checks the POM and prints BUILD SUCCESS. Which path in the local repository install copies the jar to is shown in the section GAV Coordinates.
Running a Phase Runs Every Phase Before It
The key rule of the Maven lifecycle: the command mvn <phase> runs every phase of the same lifecycle from the first one up to and including the named phase. That is why mvn package copies resources, compiles the code and runs the tests by itself, while the phases after package (verify, install, deploy) do not run.
mvn package In the Maven output, every executed goal is marked with a line like --- plugin:version:goal (execution-id) @ project ---, and these lines show what actually happened. Output (fragment, Maven 3.9; plugin versions depend on the Maven version):
[INFO] --- resources:3.3.1:resources (default-resources) @ shop ---
[INFO] --- compiler:3.13.0:compile (default-compile) @ shop ---
[INFO] --- resources:3.3.1:testResources (default-testResources) @ shop ---
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ shop ---
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] Building jar: /home/user/shop/target/shop-1.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS Phases without bound goals (validate, initialize, generate-sources, prepare-package and others) do not appear in the log: nothing runs in them for a jar project. The default- prefix in parentheses means the goal comes from the standard bindings, not from your pom.xml. If a test fails, Maven stops at the test phase and no jar is created.
mvn deploy goes through all default phases, but in a training project it fails at the very last one: there is no target repository configured.
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.1.2:deploy (default-deploy) on project shop:
Deployment failed: repository element was not specified in the POM inside distributionManagement element
or in -DaltDeploymentRepository=id::url parameter Maven Goals vs Phases: What Is the Difference
A phase is a step of the lifecycle and does nothing on its own. A goal is a concrete plugin action, such as compiler:compile or jar:jar. When you run a phase, Maven executes all previous phases of the same lifecycle and the goals bound to them. When you run a goal, only that goal executes, without any of the earlier steps.
| Aspect | Phase | Goal |
|---|---|---|
| Example | package | jar:jar |
| Who does the work | The goals bound to the phase | The plugin (maven-jar-plugin) |
| What runs when you call it | Every phase of the lifecycle up to and including it | Only this goal |
| Syntax | mvn package | mvn jar:jar or mvn org.apache.maven.plugins:maven-jar-plugin:3.4.1:jar |
The short form jar:jar is the plugin prefix followed by the goal name. Goals are called directly for one-off actions that are not part of the build: mvn dependency:tree, mvn help:effective-pom, mvn versions:display-dependency-updates. Phases and goals can be mixed in one command: mvn clean compile dependency:tree runs the clean lifecycle, then default up to compile, and only then the dependency:tree goal.
This explains the problem from the beginning of the lesson: mvn clean jar:jar deleted target and immediately packaged a classes directory that did not exist. Output:
[INFO] --- clean:3.2.0:clean (default-clean) @ shop ---
[INFO] Deleting /home/user/shop/target
[INFO] --- jar:3.4.1:jar (default-cli) @ shop ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: /home/user/shop/target/shop-1.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS The execution id default-cli shows that the goal was called from the command line rather than by a phase. To build a jar, run the phase: mvn clean package.
How Plugin Goals Are Bound to Phases
Which goals run in each phase is determined by the <packaging> element in pom.xml: Maven has a built-in set of default bindings (the lifecycle mapping) for each packaging type. That is why a jar project builds without a single declared plugin, while war and pom do different things in the same phases. You can bind your own goals to any phase in the <build><plugins> section.
Default bindings for packaging = jar (Maven 3.9.x):
| Phase | Plugin goal | What it does |
|---|---|---|
process-resources | resources:resources | Copies src/main/resources to target/classes |
compile | compiler:compile | Compiles src/main/java |
process-test-resources | resources:testResources | Copies src/test/resources to target/test-classes |
test-compile | compiler:testCompile | Compiles src/test/java |
test | surefire:test | Runs unit tests |
package | jar:jar | Creates the jar in target |
install | install:install | Copies the jar and POM into ~/.m2/repository |
deploy | deploy:deploy | Publishes to the remote repository |
For war, the package phase runs war:war instead of jar:jar. For pom (parent and aggregator POMs), only install and deploy remain: there is no compilation and no testing. How the packaging value is set and what else it affects is covered in the section Maven Packaging; binding your own goal to a phase with <executions> is covered in a separate lesson of this module on Maven plugins (see also the official Guide to Configuring Plug-ins). If several goals are bound to the same phase, the goals from the default bindings run first, then those declared in pom.xml, in declaration order.
The table above holds for a "clean" project. To check the actual bindings of your project (including the parent POM and plugin versions), use mvn help:describe:
mvn help:describe -Dcmd=compile Output (fragment):
[INFO] 'compile' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile
It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
...
* process-resources: org.apache.maven.plugins:maven-resources-plugin:3.3.1:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile
...
* test: org.apache.maven.plugins:maven-surefire-plugin:3.5.2:test
* package: org.apache.maven.plugins:maven-jar-plugin:3.4.1:jar Not defined means no goal is bound to the phase. To describe the goals of a specific plugin, run mvn help:describe -Dplugin=surefire -Ddetail. The official list of phases and bindings is in the Maven documentation Introduction to the Build Lifecycle.
Unit vs Integration Tests: Surefire and Failsafe
Unit tests are run by maven-surefire-plugin in the test phase; it is enabled by default. Integration tests are run by maven-failsafe-plugin, which you have to declare in pom.xml. It works in the integration-test and verify phases, so integration tests are run with mvn verify, not mvn integration-test.
| Aspect | Surefire (unit tests) | Failsafe (integration tests) |
|---|---|---|
| How it is enabled | By default, bound to the test phase | Only explicitly in <build><plugins> |
| Phases | test | integration-test (run) and verify (check the result) |
| Default class name patterns | Test*, *Test, *Tests, *TestCase | IT*, *IT, *ITCase |
| If a test fails | The build stops immediately at test | The build continues to post-integration-test (the environment gets shut down) and fails at verify |
| Reports | target/surefire-reports | target/failsafe-reports |
| Skip | -DskipTests | -DskipITs (integration tests only) or -DskipTests (all tests) |
Integration tests run after package because they often need the built artifact and an external environment: a database, a container, a running server. Starting the environment is bound to pre-integration-test, stopping it to post-integration-test. The minimal failsafe setup:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> No <phase> element is needed here: the failsafe goals have default phases that match their names. Output of mvn verify for a project with an OrderRepositoryIT class (fragment):
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] --- failsafe:3.5.2:integration-test (default) @ shop ---
[INFO] Running com.examclouds.shop.OrderRepositoryIT
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] --- failsafe:3.5.2:verify (default) @ shop ---
[INFO] BUILD SUCCESS Configuration details are in the Maven Failsafe Plugin documentation. Where test classes and test resources go is shown in the section Maven Project Structure.
Why not mvn integration-test
mvn integration-test stops at the phase of the same name: post-integration-test does not run, so the database or server started for the tests is left running. The failsafe:verify goal in the verify phase does not run either, so failing integration tests do not fail the build — you can get BUILD SUCCESS with red tests in the report. Run mvn verify (or a later phase: install, deploy).
Common Build Commands: mvn clean install, verify, -DskipTests
For everyday work a handful of commands is enough: mvn clean verify fully checks the project, mvn clean install does the same and also installs the artifact into the local repository, -DskipTests builds without running tests, and -pl builds selected modules of a multi-module project.
| Command | What it does | When to use it |
|---|---|---|
mvn clean verify | Cleans, compiles, runs all tests and checks; the artifact stays in target | Checking before a commit, CI builds |
mvn clean install | The same plus copying the artifact into ~/.m2/repository | When another project on this machine needs the built module |
mvn package -DskipTests | Tests are compiled but not run; the log shows Tests are skipped. | Getting a jar quickly when the tests have already been checked |
mvn package -Dmaven.test.skip=true | Tests are neither compiled nor run | Rarely: when the test code temporarily does not compile |
mvn -pl shop-core -am install | Builds the shop-core module and the modules it depends on (-am, also make) | Multi-module project, only one module is needed |
mvn -pl shop-core -amd verify | Builds the module and the modules that depend on it (-amd, also make dependents) | Checking that a change did not break dependent modules |
mvn verify -rf :shop-web | Resumes a failed build from the shop-web module (resume from) | After fixing an error in the middle of a multi-module build |
mvn -o package / mvn -U package | Offline mode / forced update check for SNAPSHOT dependencies | No network / you need a fresh SNAPSHOT from the remote repository |
mvn package -DskipTests Output (fragment):
[INFO] --- surefire:3.5.2:test (default-test) @ shop ---
[INFO] Tests are skipped.
[INFO] --- jar:3.4.1:jar (default-jar) @ shop ---
[INFO] BUILD SUCCESS -DskipTests is preferable to -Dmaven.test.skip=true: the test code is at least compiled, so an error in it does not go unnoticed. Besides, if another module uses your test classes as a test-jar, with maven.test.skip that jar is not created, and the dependent module fails when resolving the dependency. The -pl option takes a module directory path or :artifactId; several modules are separated by commas.
mvn clean install out of habit
Developers type mvn clean install almost by reflex, but to check a project mvn clean verify is enough: it runs the same tests and checks without filling the local repository with intermediate builds. In a multi-module project, Maven passes the output of sibling modules to each other within one build (the reactor), so there is no need to install them into ~/.m2 for that. You need install when the artifact must be picked up by another, separately built project.
What Changes in Maven 4
In Maven 4 (still in preview as of November 2026; the 3.x line remains the recommended one), the lifecycle is compatible with Maven 3: the same phases and the same mvn clean verify and mvn install commands. The main change is that the pre-* and post-* phases are replaced by before: and after: hooks available for any phase (for example, before:integration-test), while the old names are kept for compatibility.
For training projects on Maven 3.9.x and 3.10.x, everything in this lesson works without changes. mvn -version shows which Maven version is installed (see How to Install Maven), and the Maven Wrapper pins the version for the whole team (see the lesson What Is Maven).
Lifecycle Mistakes and How to Fix Them
Lifecycle mistakes rarely look like errors: the build passes, but something other than what you expected was executed. The table shows what was run, what you get and how to fix it.
| What was run | What happens | How to fix it |
|---|---|---|
mvn clean jar:jar instead of mvn clean package | BUILD SUCCESS, but the jar has no classes; JAR will be empty in the log | Run the phase, not the goal: mvn clean package |
Created OrderRepositoryIT but did not add failsafe | The test never runs anywhere: surefire does not pick up *IT, and failsafe is absent | Declare maven-failsafe-plugin with the integration-test and verify goals |
mvn integration-test | The test environment is not stopped, failing ITs do not fail the build | mvn verify |
mvn surefire:test after changing the code | Tests run against stale .class files: nothing was compiled | mvn test |
mvn deploy in a project without <distributionManagement> | Deployment failed: repository element was not specified in the POM inside distributionManagement element | For a local check, verify or install is enough; to publish, configure a repository |
-Dmaven.test.skip=true in a project with a test-jar | The dependent module cannot find the test jar | Use -DskipTests |
Deleted or renamed a class and built without clean | The old .class file may stay in target/classes and end up in the jar | mvn clean package |
Frequently Asked Questions
Do I need to run mvn clean before every build?
No. The compiler recompiles changed classes by itself, and a build without clean is faster. You need clean when classes were deleted or renamed (their old .class files may stay in target), after changing the Java version or plugin versions, and on CI, where every build should start from scratch.
How do I run a single test class or test method?
With the surefire -Dtest parameter: mvn test -Dtest=OrderServiceTest or mvn test -Dtest=OrderServiceTest#create; patterns are allowed (-Dtest=Order*Test). For integration tests, -Dit.test=OrderRepositoryIT does the same with mvn verify. In a multi-module project add -Dsurefire.failIfNoSpecifiedTests=false, otherwise modules without such a test fail the build.
Can I add my own phase to the Maven lifecycle?
Usually there is no need: you bind your goal to one of the existing phases, for example a code generator to generate-sources or a style check to verify. A custom lifecycle or a custom packaging type can only be defined in a Maven extension, which is a job for plugin authors. Binding goals to phases is shown in the official Guide to Configuring Plug-ins.
Why does mvn install in one project not update the dependency in another?
Most often the versions do not match: the second project refers in its <dependency> to a different version than the one in the first project pom.xml. If the versions match, the IDE may not have re-imported the project — reload the Maven project in the IDE. For a SNAPSHOT from a remote repository, use mvn -U. Where exactly install puts the files is shown in the section GAV Coordinates.
Comments