Maven (Part 8) - Build Lifecycle
Maven (Part 8) - Build Lifecycle

Maven (Part 8) - Build Lifecycle

in

Maven (Part 8): Build Lifecycle

Maven is built upon the fundamental concept of build lifecycles. This means that the process of building and deploying specific artifacts (projects) is well-defined.

For those building projects, this implies that they need to learn just a small set of commands to build any Maven project, and the POM ensures they get the desired results.

There are three built-in build lifecycles: default, clean, and site. The default lifecycle deals with project deployment, the clean lifecycle handles project cleaning, and the site lifecycle deals with creating the project’s website.

Build Lifecycles Comprise Several Phases

Each build lifecycle is defined by a list of different build phases, where each build phase represents a step in the lifecycle.

For example, the default lifecycle includes the following phases (for a complete list of lifecycle phases, refer to the Lifecycle Reference):

  • validate - Validate the project is correct and all necessary information is available
  • compile - Compile the source code of the project
  • test - Test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package - Package the compiled code in its distributable format, such as a JAR.
  • verify - Run any checks on results of integration tests to ensure quality criteria are met
  • install - Install the package into the local repository, for use as a dependency in other projects locally
  • deploy - Done in the build environment, copy the final package to the remote repository for sharing with other developers and projects.

These lifecycle phases (along with other lifecycle phases not shown here) are executed sequentially to complete the default lifecycle. Given the above lifecycle phases, this means that when using the default lifecycle, Maven will first validate the project, then attempt to compile the source code, run tests against the compiled code, package the binaries (like jar), run integration tests against that package, verify the integration tests, install the verified package into the local repository, and finally deploy the installed package to a remote repository.

Common Command Line Invocations

You should choose the phase that matches your expected outcome. If you want your jar, run package. If you want to run unit tests, run test.

If you’re unsure what you want, the preferred invocation is:

mvn verify

This command will sequentially execute each default lifecycle phase (validate, compile, package, etc.) before the verify phase. You only need to call the last build phase you want to execute, which in this case is verify. In most cases, the effect is the same as package. However, if there are integration tests (test), those will also be executed. Additionally, some extra checks can be done in the validate phase, such as ensuring the code is written according to predefined validation rules.

For clean and tidy build and deployment to a shared repository in the build environment, use:

mvn clean deploy

The same command can be used in multi-module situations (i.e., a project with one or more sub-projects). Maven will iterate through each submodule, performing the clean and then the deploy (including all the preceding build phases).

Build Phases Comprise Plugin Goals

However, while the build phases are responsible for specific steps in the build lifecycle, how they fulfill their duties may vary. This is achieved by declaring plugin goals bound to these build phases.

Plugin goals represent specific tasks (finer than build phases) that help in building and managing a project. They can be bound to 0 or more build phases. Goals not bound to any build phase can be executed directly outside of the build lifecycle. The order of execution depends on the invocation order of goals and build phases. For example, consider the following command. The clean and package parameters are build phases, while dependency:copy-dependencies is a (plugin) goal.

mvn clean dependency:copy-dependencies package

When this goal is executed, it first performs the clean phase (meaning it will run all the preceding phases of the clean lifecycle, as well as the clean phase itself), then executes the dependency:copy-dependencies goal, and finally performs the package phase (along with all the preceding build phases of the default lifecycle).

Additionally, if a goal is bound to one or more build phases, it will be called in all of those phases.

Furthermore, a build phase can also be bound to 0 or more goals. If a build phase is not bound to any goals, then that build phase will not execute. But if it is bound to one or more goals, then all those goals will execute.

(Note: In Maven 2.0.5 and above, multiple goals bound to a phase will execute in the order declared in the POM, but multiple instances of the same plugin are not supported. In Maven 2.0.11 and above, multiple instances of the same plugin are grouped together and executed in order).

Certain Phases Are Typically Not Invoked Directly from the Command Line

Phases named with hyphens (pre-*, post-*, or process-*) are typically not directly invoked from the command line. These phases are for organizing the build process, resulting in intermediate artifacts outside of the build process being useless. The environment might be left in a suspended state when invoking integration tests (integration-test).

Code coverage tools (like Jacoco) and execution container plugins (like Tomcat, Cargo, and Docker) bind goals to the pre-integration-test phase to prepare the integration test container environment. These plugins also bind goals to the post-integration-test phase to collect coverage statistics or tear down the integration test container.

Fault-safe and code coverage plugins bind goals to the integration-test and verify phases. The end result is that test and coverage reports are available after the verify phase. If integration tests (integration-test) are invoked from the command line, no reports will be generated. Worse yet, the integration test container environment will be left suspended; Tomcat web servers or Docker instances will still be running, and Maven might not even terminate on its own.

Setting Up Projects to Use Build Lifecycles

Build lifecycles are straightforward and easy to use, but how do you assign tasks to each build phase when you build a Maven project?

Packaging

The first and most common method is to set the packaging for the project through the <packaging> POM element, which has the same name as the phase. Some valid packaging values include jar, war, ear, and pom. If no packaging value is specified, it defaults to jar.

Each packaging contains a list of goals to be bound to the build lifecycle. For example, the jar packaging binds the following goals to the build phases of the default lifecycle.

Phase jar Packaging plugin:goal
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` |

These are almost a set of standard bindings; however, some packaging types handle them differently. For example, metadata-only projects (packaging value pom) only bind goals to the install and deploy phases (for a full list of bindings of goals to build phases for certain packaging types, see Lifecycle Reference).

Note that to make certain packaging types available, you might also need to include specific plugins in the <build> section of the POM, specifying <extensions>true</extensions> for that plugin. Plexus plugin is an example that requires this, providing plexus-application and plexus-service packaging.

Plugins

The second method to add goals to phases is by configuring plugins in the project. Plugins are artifacts that provide goals to Maven. Additionally, a plugin may have one or more goals, where each goal represents an ability of that plugin. For example, the compiler plugin has two goals: compile and testCompile. The compiler plugin compiles the source code of the project.

As you’ll see in later sections, plugins can contain instructions on which phase to bind goals to. Note that just adding plugin information alone is not enough; you must also specify the goals to run during the build process.

The configured goals will be added to the goals already bound to the selected packaging lifecycle. If a specific phase is bound to multiple goals, the order used is to first execute the goals from the packaging and then execute the goals configured in the POM. Note that you can use the <executions> element to control the order of goals for a specific goal.

For example, the Modello plugin by default binds its modello:java goal to the generate-sources phase (the modello:java goal generates Java source code). So, to use the Modello plugin and have it generate source code from models and include it in the build, you would need to add the following to the <plugins> section of the POM under <build>:

 <plugin>
   <groupId>org.codehaus.modello</groupId>
   <artifactId>modello-maven-plugin</artifactId>
   <version>1.8.1</version>
   <executions>
     <execution>
       <configuration>
         <models>
           <model>src/main/mdo/maven.mdo</model>
         </models>
         <version>4.0.0</version>
       </configuration>
       <goals>
         <goal>java</goal>
       </goals>
     </execution>
   </executions>
 </plugin>

You might wonder why the <executions> element is used. This allows you to run the same goal multiple times with different configurations as needed. You can also give an ID to individual executions, so when merging or applying configuration files, you can control whether the goal configuration is merged or transformed into additional executions.

When given multiple executions that match a specific phase, those executions will be executed in the order specified in the POM, with inherited executions executing first.

Now, for modello:java, for example, it only makes sense in the generate-sources phase. But some goals can be used in multiple phases and might not have sensible defaults. For these goals, you can specify the phase yourself. For example, let’s assume there’s a display:time goal that displays the current time on the command line, and you want it to run in the process-test-resources phase to show when tests are starting. You can configure it like this:

 <plugin>
   <groupId>com.mycompany.example</groupId>
   <artifactId>display-maven-plugin</artifactId>
   <version>1.0</version>
   <executions>
     <execution>
       <phase>process-test-resources</phase>
       <goals>
         <goal>time</goal>
       </goals>
     </execution>
   </executions>
 </plugin>

Lifecycle Reference

clean Lifecycle

Phase Description
pre-clean Execute processes needed prior to the actual project cleaning
clean Remove all files generated by the previous build
post-clean Execute processes needed to finalize the project cleaning

site Lifecycle

Phase Description
pre-site Execute processes needed prior to the actual project site generation
site Generate the project’s site documentation
post-site Execute processes needed to finalize the site generation, and to prepare for site deployment
site-deploy Deploy the generated site documentation to the specified web server

default Lifecycle

Phase Description
validate Validate the project is correct and all necessary information is available
initialize Initialize build state, e.g., set properties or create directories
generate-sources Generate source code to include in compilation
process-sources Process the source code, e.g., filter any values
generate-resources Generate resources included in the package
process-resources Copy and process resources into the target directory, ready for packaging
compile Compile the project’s source code
process-classes Post-process the generated files, e.g., bytecode enhancement on Java classes
generate-test-sources Generate test source code to include in compilation
process-test-sources Process the test source code, e.g., filter any values
generate-test-resources Create resources for testing
process-test-resources Copy and process test resources into the target directory
`  

test-compile | Compile the test source code into the test target directory | | process-test-classes | Post-process the generated files from test compilation | | test | Run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed | | prepare-package | Perform any operations necessary to prepare a package before the actual packaging | | package | Package the compiled code in its distributable format, e.g., JAR | | pre-integration-test | Perform actions required before integration tests are executed. This may involve setting up the required environment, etc. | | integration-test | Process and deploy the package if necessary into an environment where integration tests can be run | | post-integration-test | Perform actions required after integration tests have been executed. This may include cleaning up the environment, etc. | | verify | Run any checks on results of integration tests to ensure quality criteria are met | | install | Install the package into the local repository, for use as a dependency in other projects locally | | deploy` | Done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects |

Built-In Lifecycle Bindings

Some phases are bound by default. For the default lifecycle, these bindings depend on the packaging value. Here are some goals bound to phases of the build.

clean Lifecycle Binding

Phase plugin:goal
clean clean:clean

site Lifecycle Binding

Phase plugin:goal
site site:site
site-deploy site:deploy

default Lifecycle Binding

Packaging ejb / ejb3 / jar / par / rar / war

Phase plugin:goal
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

Packaging ear

Phase plugin:goal
generate-resources ear:generate-application-xml
process-resources resources:resources
package ear:ear
install install:install
deploy deploy:deploy

Packaging maven-plugin

Phase plugin:goal
generate-resources plugin:descriptor
process-resources resources:resources
compile compiler:compile
process-test-resources resources:testResources
test-compile compiler:testCompile
test surefire:test
package jar:jar and plugin:addPluginArtifactMetadata
install install:install
deploy deploy:deploy

Packaging pom

Phase plugin:goal
package  
install install:install
deploy deploy:deploy