The Maven Lifecycle

Know what mvn package and mvn verify actually run, how plugins bind to phases, and which commands belong in CI.

Syntaxmvn [options] [lifecycle-phase | plugin:goal ...] mvn clean verify # clean lifecycle, then default up to verify

Maven 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

PhasePlugin goal bound by default
process-resourcesresources:resources copies src/main/resources
compilecompiler:compile
test-compilecompiler:testCompile
testsurefire:test runs unit tests
packagejar:jar
integration-test, verifynothing until you add a plugin such as Failsafe
installinstall:install copies the JAR into ~/.m2/repository
deploydeploy: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

Example · xml
<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.

Example · bash
./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 lifecycle

Failing 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.

Example · xml
<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.

Example · bash
#!/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

  • Be the first to comment on this lesson.