Maven is the wonderful, confusing build tool for Java.

Installation

Installing Maven through Software Collections on CentOS:

$ sudo yum install -y centos-release-scl
$ sudo yum install -y rh-maven35
$ scl enable rh-maven35 bash

Basic, non-packaged installation on RHEL:

$ sudo mkdir -p /usr/share/maven /usr/share/maven/ref
$ sudo curl -fsSL -o /tmp/apache-maven.tar.gz http://mirror.ox.ac.uk/sites/rsync.apache.org/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz
$ sudo tar -zxf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1
$ sudo rm -f /tmp/apache-maven.tar.gz
$ sudo ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

Terminology

  • When you run Maven, you can specify a goal or a phase:
    • When executing Maven with a phase, it will pass/execute all phases up to and including the given phase.
    • When executing Maven with a goal, it will pass/execute all phases up to the phase for that goal.
  • Goals are executed in phases; Maven’s default lifecycle bindings show which goals will run in which phases.

Properties of artifacts

  • <optional>true</optional> on a dependency means that the dependency will not get pulled down (as transitive dependency) by default - unless, for example, the artifact is downloaded explicitly, or using dependency:go-offline.

Lifecycles and build phases

There are three built-in lifecycles:

  • default
  • clean
  • site

Standard build phases

The standard build phases for the default lifecycle are:

  • validate
  • initialize
  • generate-sources - generate any source code for inclusion in compilation
  • process-sources
  • generate-resources - generate resources for inclusion in the package
  • 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

Lifecycle bindings

  • The packaging type dictates which goals are bound to each phase.
  • e.g. jar: binds jar:jar to the package phase, surefire:test to the test phase, etc.

POMs

The Maven Super POM is located in:

lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml

Repositories

Here are some standard repository IDs used by Maven:

Cookbook

Getting help

Getting detailed help on a plugin goal and parameters:

$ mvn help:describe -Dcmd=bundle:baseline-report -Ddetail

Working with plugin goals

To execute a specific execution defined in your POM:

$ mvn prefix:goal@your-execution-id

Getting the effective POM

The effective POM is the POM which is ultimately generated from all of your parent relationships, dependencyManagement sections and so on. It can show exactly which versions of artifacts you’re using:

$ mvn help:effective-pom
$ mvn help:effective-pom -Doutput=fileToWriteTo.xml

Working offline

Download the artifacts required to build a project into a temporary location and produce a TAR:

$ rm -rf ~/.m2/repository
$ mvn dependency:go-offline -Dmaven.repo.local=/path/to/repository
$ tar cvfz offline-repository.tgz /path/to/repository

Download a specific artifact to a temporary location:

$ mvn dependency:get \
    -Dartifact=org.arquillian.cube:arquillian-cube-openshift:1.9.0 \
    -Dmaven.repo.local=/home/tdonohue/tmp/arq190

Download a specific artifact and its dependencies to a temporary location (NB: This will also attempt to download any dependencies marked as optional=true) and produce a TAR:

$ mvn dependency:get -Dartifact=org.arquillian.cube:arquillian-cube-openshift:1.9.0
$ mvn -f /home/tdonohue/.m2/repository/org/arquillian/cube/arquillian-cube-openshift/1.9.0/arquillian-cube-openshift-1.9.0.pom \
    dependency:go-offline \
    -Dmaven.repo.local=/path/to/temporary/location
$ tar cvfz artifact-dependencies.tgz /path/to/temporary/location

Copy a project’s dependencies:

$ mvn dependency:copy-dependencies

Configure a mirror and test downloading an artifact

To see whether your Nexus repository is correctly configured to mirror artifacts, create a settings.xml file with a mirror configured to point to your Nexus. Then, use that settings file to try to download an artifact:

cat << EOF > ~/.m2/settings-test-mirror.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd" xmlns="http://maven.apache.org/SETTINGS/1.1.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <mirrors>
    <mirror>
      <mirrorOf>*</mirrorOf>
      <name>A Nexus to Mirror All The Artifacts</name>
      <url>https://your-nexus.example.com/repositories/maven-public/</url>
      <id>mynexus</id>
    </mirror>
  </mirrors>
</settings>
EOF

mvn -s ~/.m2/settings-test-mirror.xml dependency:get \
    -Dartifact=org.arquillian.cube:arquillian-cube-openshift:1.9.0

This is an alternative to trying to specify a Maven mirror at the command line (because I don’t think that’s possible….?).

Deployment

Deploy the compiled artifact to a repository, specifying the URL explicitly at the command line:

$ mvn clean deploy -DaltDeploymentRepository=internalSnapshots::default::http://localhost:8081/nexus/content/repositories/snapshots/
$ mvn clean deploy -DaltDeploymentRepository=internalReleases::default::http://localhost:8081/nexus/content/repositories/releases/

Debugging

To debug a Maven goal, use the mvnDebug command - this will start a debug listener on port 8000:

$ mvnDebug <plugin:goal>

Analysing dependencies

To find which dependency pulls in a particular transitive dependency:

$ mvn dependency:tree -Dincludes=org.apache.tomcat:tomcat-jdbc
...
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ my-application ---
[INFO] com.example:my-application:jar:1.0-SNAPSHOT
[INFO] \- org.springframework.boot:spring-boot-starter-jdbc:jar:1.5.16.RELEASE:compile
[INFO]    \- org.apache.tomcat:tomcat-jdbc:jar:8.0.36.redhat-30:compile

Plugin configuration

Overriding plugin configuration in a child POM

In a child POM, to override the configuration for a plugin which may be defined on a parent, use the combine.self attribute, either on the configuration attribute or its children:

<configuration combine.self="override">
  <!-- Your config here will override config defined in the parent POM -->
</configuration>

Plugins

Failsafe Plugin for integration testing

Sample configuration for a Spring Boot application. This will run integration tests following the naming standard (i.e. *IT.java) when executing mvn verify:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <!-- Configure plugin to play nicely with Spring Boot -->
        <!-- https://github.com/spring-projects/spring-boot/issues/6254#issuecomment-307151464 -->
        <classesDirectory>${project.build.outputDirectory}</classesDirectory>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Troubleshooting

When fetching an artifact from a local Maven repository, you get the error: “Could not find artifact org.xx.x:xx-xx-xx:jar:2.23.2 in xxx” - but you know it should work:

  • Maven has probably already tried once to fetch the artifact, and cached the failure. Re-run the build with Maven’s -U switch to force updates (try again).

maven-failsafe-plugin does not run any integration tests, even when there are tests matching the pattern **/**IT.java:

  • Failsafe Plugin does not play nicely with Spring Boot. Add this line into the configuration section: <classesDirectory>${project.build.outputDirectory}</classesDirectory>