The Maven Lifecycle
Know what mvn package and mvn verify actually run, how plugins bind to phases, and which commands belong in CI.
mvn [options] [lifecycle-phase | plugin:goal ...]
mvn clean verify # clean lifecycle, then default up to verifyMaven builds through lifecycles: ordered lists of phases. You name a phase and Maven runs every phase before it, in order. mvn package therefore validates, compiles, runs the unit tests and then builds the JAR. You cannot reach package without passing through test, unless you skip the tests on purpose.
The three lifecycles
clean deletes target/. default builds, tests and publishes. site generates project documentation and is rarely used for services. mvn clean verify runs the clean lifecycle and then the default lifecycle up to verify.
The default lifecycle for a JAR
| Phase | Plugin goal bound by default |
|---|---|
process-resources | resources:resources copies src/main/resources |
compile | compiler:compile |
test-compile | compiler:testCompile |
test | surefire:test runs unit tests |
package | jar:jar |
integration-test, verify | nothing until you add a plugin such as Failsafe |
install | install:install copies the JAR into ~/.m2/repository |
deploy | deploy:deploy uploads it to a remote repository |
Phases do nothing on their own; plugin goals bound to them do the work. The packaging type decides the default bindings, and your POM adds more with <executions>. You can also run a single goal directly, such as mvn dependency:tree, without any phase.
Unit tests and integration tests
Surefire runs classes named *Test, Test*, *Tests or *TestCase in the test phase, and any failure fails the build before a JAR is made. Failsafe runs *IT, IT* and *ITCase in integration-test, after the JAR exists, and only fails the build in verify. That gap lets post-integration-test stop containers or servers first. Run integration tests with mvn verify; calling mvn integration-test skips the step that reports failures.
Which command, when
CI should run verify: everything is compiled, tested and packaged, and your artifact is not copied into ~/.m2. install is only needed when a separate build on the same machine must find your artifact in ~/.m2, so the reflexive mvn clean install is mostly wasted time. Inside one multi-module build, Maven passes module outputs along without installing them.
-DskipTests compiles the tests but does not run them. -Dmaven.test.skip=true does not even compile them, which breaks modules that depend on another module's test JAR. In CI also add -B (batch mode, no colours or prompts) and -ntp (no download progress) to keep logs readable.
Example
<build>
<plugins>
<!-- Integration tests (*IT.java) after packaging; failures reported in verify -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.6</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>When to use it
- Run fast unit tests on every push and database-backed integration tests in the same mvn verify call.
- Stop a build at once when a developer's machine has an older JDK than the service needs.
- Cut minutes off CI by replacing clean install with verify in a pipeline that never reuses ~/.m2.
More examples
Commands worth knowing
Naming a phase runs all phases before it, while naming a goal runs only that goal. The -Dtest filter is read by Surefire, so it applies to the test phase.
./mvnw clean verify # full build with unit and integration tests
./mvnw test -Dtest=OrderServiceTest # one test class
./mvnw test -Dtest='OrderServiceTest#rejectsEmptyCart'
./mvnw package -DskipTests # compile tests, do not run them
./mvnw -o package # offline: use only ~/.m2
./mvnw -T 1C verify # build modules in parallel, one thread per core
./mvnw dependency:tree # a goal on its own, no lifecycleFailing fast in the validate phase
The enforce goal binds to validate by default, the first phase, so a build on the wrong JDK stops in a second with a clear message instead of a confusing compiler error later.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version> <!-- or newer -->
<executions>
<execution>
<id>enforce-versions</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireJavaVersion><version>[25,)</version></requireJavaVersion>
<requireMavenVersion><version>[3.9,)</version></requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>A CI build step
verify is the right end point for CI because it runs every test and packages the JAR without installing it into the local ~/.m2 repository, which only receives downloaded dependencies. The XML reports in those folders are what CI servers parse to show failed tests.
#!/usr/bin/env bash
set -euo pipefail
# Batch mode, no download progress noise, fail on any test failure
./mvnw -B -ntp clean verify
# Surefire and Failsafe reports, for the CI server's test view
ls target/surefire-reports target/failsafe-reports
Discussion