Maven
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 Linux:
MAVEN_VERSION=3.6.3
sudo mkdir -p /usr/share/maven /usr/share/maven/ref
sudo curl -fsSL -o /tmp/apache-maven.tar.gz https://downloads.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-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
Concepts
Goals and phases
-
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.
Defaults
Built-in lifecycles
There are three built-in lifecycles:
default
clean
site
default
build phases
The standard build phases for the default
lifecycle are:
validate
initialize
generate-sources
- generate any source code for inclusion in compilationprocess-sources
generate-resources
- generate resources for inclusion in the packageprocess-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
: bindsjar:jar
to thepackage
phase,surefire:test
to thetest
phase, etc.
Super POM
All projects implicitly inherit from the Maven Super POM. This is located in:
lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml
The Super POM defines things like:
- default repository -
central
(Maven Central) - this is defined in the Super POM, and points to http://repo1.maven.org - default plugin repository - also
central
, pointing to https://repo.maven.apache.org/maven2 - default build directories - e.g.
sourceDirectory
(src/main/java
),target/
, etc.
Managing dependencies
Optional dependencies
<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
.
Multi-module projects
An aggregator POM provides composition - Use an aggregator project to join several modules together under one project. An aggregator doesn’t include any information about dependencies, it’s only a way of grouping projects together.
- It is better to not inherit from aggregator POMs
A parent POM provides inheritance - Use this to specify dependencies/dependencyManagement sections.
This example project setup shows an Aggregator POM and a Parent POM.
my-app/ (Aggregator POM)
├── pom.xml
├── module-1/
│ └── pom.xml
├── module-2/
│ └── pom.xml
└── app-parent/ (Parent POM)
└── pom.xml
Cookbook
Getting help for a Maven plugin
Listing all goals and parameters:
$ mvn help:describe -DgroupId=org.apache.maven.plugins \
-DartifactId=maven-war-plugin \
-Ddetail=true
Getting detailed help on a plugin goal and its parameters:
$ mvn help:describe -Dcmd=<plugin-prefix>:<goal> -Ddetail
Working with plugin goals
To run a specific execution
which you’ve defined in your POM:
$ mvn <plugin-prefix>:<goal>@your-execution-id
Binding a plugin goal to a Maven phase
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-restdsl-openapi-plugin</artifactId>
<version>2.23.2.fuse-770010-redhat-00001</version>
<executions>
<!-- Create an execution and bind it to Maven's 'generate-sources' phase -->
<execution>
<id>generate-camel-stuff</id>
<goals>
<goal>generate-with-dto</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<!-- .... configure as usual .... -->
</configuration>
</plugin>
Getting the effective POM - show all plugin and dependency versions
The effective POM is the POM which is ultimately generated by Maven, by resolving all of your parent relationships, dependencyManagement (BOM) sections, and so on. It shows exactly which versions of artifacts and plugins you’re using:
$ mvn help:effective-pom
$ mvn help:effective-pom -Doutput=fileToWriteTo.xml
Installing a file into local repository
mvn install:install-file -Dfile=path/to/myjar.jar -DgroupId=com.example -DartifactId=artifact-name -Dversion=1.0.0 -Dpackaging=jar
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 temporary 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….?).
Deploying an artifact to a repository
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:
$ alias mvnDebug=/usr/share/maven/bin/mvnDebug
$ 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>