
This chapter covers the central concept of Maven—The Project Object
Model or POM. The POM is where a
project's identity and structure are declared, builds are configured, and
projects are related to one another. The presence of a
pom.xml file defines a Maven project.
Maven projects, dependencies, builds, artifacts: all of these are
objects to be modeled and described. These projects, dependencies, builds,
and artifacts are described by an XML file called a
Project Object Model. The POM tells Maven what sort of
project it is dealing with and how to modify default behavior to generate
output from source. In the same way a Java Web Application has a
web.xml which describes, configures, and customizes
the application, a Maven project is defined by the presence of a
pom.xml. It is a descriptive declaration of a project
for Maven, it is the figurative "map" which Maven needs to understand what
it is looking at when it builds your project.
You could also think of the pom.xml as being
analogous to a Makefile or an Ant
build.xml. When you are using GNU
make to build something like MySQL, you'll usually have a file named
Makefile which contains explicit instructions for
building a binary from source. When you are using Apache Ant, you likely
have a file named build.xml which contains explicit
instructions for cleaning, compiling, packaging, and deploying an
application. Make, Ant, and Maven are similar in that they rely on the
presence of a commonly named file like Makefile,
build.xml, or pom.xml, but this
is where the similarities end. If you look at a Maven
pom.xml, the majority of the POM
is going to deal with descriptions: Where is the source code? Where are
the resources? What is the packaging? If you look at an Ant
build.xml file, you see something entirely different.
You'll see explicit instructions for tasks like compiling a set of Java
classes. The Maven POM is declarative, and while you
can certainly choose to include some procedural customizations via the
Maven Ant plugin, for the most part, you will not need to get into the
gritty procedural details of your project's build.
The POM is also not specific to building Java projects. While most of the examples in this book are geared towards Java applications, there is nothing Java-specific in the definition of a Maven Project Object Model. While Maven's default plugins are targeted at building JAR artifacts from a set of source, tests, and resources, there is nothing preventing you from defining a POM for a project that contains C# sources and produces some proprietary Microsoft binary using Microsoft tools. Similarly, there is nothing stopping you from defining a POM for a technical book. In fact, the source for this book and this book's examples is captured in a multi-module Maven project which uses one of the many Maven Docbook plugins to apply the standard Docbook XSL to a series of chapter XML files. Others have created Maven plugins to build Adobe Flex code into SWCs and SWFs, and yet others have used Maven to build projects written in C.
We've established that the POM describes and declares, it is unlike Ant or Make in that it doesn't provide explicit instructions, and we've noted that POM concepts are not specific to Java. Diving into more specifics, take a look at Figure 9.1, “The Project Object Model” for a survey of the contents of a POM.
The POM contains four-categories of description and configuration, they are:
This includes a project's name, the URL for a project, the sponsoring organization, a list of developers and contributors along with the license for a project.
In this section, we customize the behavior of the default Maven build. We can change the location of source and tests, we can add new plugins, we can attach plugin goals to the lifecycle, and we can customize the site generation parameters.
The build environment consists of profiles which can be
activated for use in different environments. For example, during
development you may want to deploy to a development server, while in
production you want to deploy to a production server. The build
environment customizes the build settings for specific environments
and is often supplemented by a custom
settings.xml in ~/.m2.
This settings file is discussed in Chapter 11, Build Profiles and
Section A.1, “Quick Overview”.
A project rarely stands alone, it depends on other projects, inherits POM settings from parent projects, defines its own coordinates, and may include submodules.
Before we dive into some examples of POMs,
let's take a quick look at the Super POM. All Maven
project POMs extend the Super POM
which defines a set of defaults shared by all projects. This Super
POM is a part of the Maven installation, and can be
found in the maven-2.0.9-uber.jar file in
${M2_HOME}/lib. If you look in this
JAR file, you will find a file named
pom-4.0.0.xml under the
org.apache.maven.project package. The Super
POM for Maven is shown in Example 9.1, “The Super POM”.
Example 9.1. The Super POM
<project>
<modelVersion>4.0.0</modelVersion>
<name>Maven Default Project</name>
<repositories>
<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<layout>default</layout>
<url>http://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
<build>
<directory>target</directory>
<outputDirectory>target/classes</outputDirectory>
<finalName>${pom.artifactId}-${pom.version}</finalName>
<testOutputDirectory>target/test-classes</testOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<scriptSourceDirectory>src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
</build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.1</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-1</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.0</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.3.1</version>
</plugin>
<plugin>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.1</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-rar-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.0-beta-7</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>2.0-beta-6</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.0.4</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1-alpha-1</version>
</plugin>
</plugins>
</pluginManagement>
<reporting>
<outputDirectory>target/site</outputDirectory>
</reporting>
</project>The Super POM defines some standard configuration variables which are inherited by all projects. Those values are captured in the annotated sections:
|
The default Super POM defines a single
remote Maven Repository with an id of central. This is the central
Maven repository that all Maven clients are configured to read from
by default. This setting can be overridden by a custom
|
|
The central Maven Repository also contains Maven plugins. The default plugin repository is the central Maven repository. Snapshots are disabled, and the update policy is set to never. When the update policy is set to never, this means that Maven will never automatically update a plugin if a new version is released. |
|
The |
|
Starting in Maven 2.0.9, default versions of core plugins have been provided in the Super POM. This was done to provide some stability for users that are not specifying versions in their POMs. |
All Maven POMs inherit defaults from the Super
POM which is introduced in Section 9.2.1, “The Super POM”. If you are just writing a simple project
that produces a JAR from some source in
src/main/java, you want to run your JUnit tests in
src/test/java, and you want to build a project site
using mvn site, you don't have to customize anything.
All you would need, in this case, is the simplest possible
POM shown in Example 9.2, “The Simplest POM”. This
POM defines a groupId,
artifactId, and version: the three
required coordinates for every project.
Example 9.2. The Simplest POM
<project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simplest-project</artifactId> <version>1</version> </project>
Such a simple POM would be more than adequate
for a simple project—a Java library that produces a
JAR file. It isn't related to any other projects, it
has no dependencies, and it lacks basic information such as a name and a
URL. If you were to create this file and then create
the subdirectory src/main/java with some source
core, running mvn package would produce a
JAR in
target/simple-project-1.jar.
This simplest POM brings us to the concept of
the "effective POM". Since POMs
can inherit configuration from other POMs, you must
always think of a Maven POM in terms of the
combination of the Super POM, plus any parent
POMs, and finally the current project's
POM. Maven starts with the Super
POM and then overrides default configuration with one
or more parent POMs. Then it overrides the resulting
configuration with the current project's POM. You end
up with an effective POM that is a mixture of various
POMs. If you want to see a project's effective
POM, you'll need to run the
effective-pom goal in the Maven Help plugin which was
introduced in Section 2.7, “Using the Maven Help Plugin”. To run the
effective-pom goal, execute the following in a
directory with a pom.xml file.
$ mvn help:effective-pomExecuting the effective-pom goal should print
out an XML document capturing the merge between the
Super POM and the POM from Example 9.2, “The Simplest POM”.
Instead of typing up a contrived set of POMs to walk you through step-by-step, you should take a look at the examples in Part I, “Maven by Example”. Maven is something of a chameleon, you can pick and choose the features you want to take advantage of. Some open source projects may value the ability to list developers and contributors, generate clean project documentation, and manage releases automatically using the Maven Release plugin. On the other hand, someone working in a corporate environment on a small team, might not be interested in the distribution management capabilities of Maven nor the ability to list developers. The remainder of this chapter is going to discuss features of the POM in isolation. Instead of bombarding you with a ten page listing of a set of related POMs, we're going to focus on creating a good reference for specific sections of the POM. In this chapter, we discuss relationships between POMs, but we don't illustrate such a project here. If you are looking for such an illustration refer to Chapter 7, Multi-module Enterprise Project.
The POM is always in a file named
pom.xml in the base directory of a Maven project.
This XML document can start with the
XML declaration or you can choose to omit it. All
values in a POM are captured as XML
elements.
A Maven project's version encodes a release
version number which is used to group and order releases. Maven versions
contain the following parts: major version, minor version, incremental
version, and qualifier. In a version, these parts correspond to the
following format:
<major version>.<minor version>.<incremental version>-<qualifier>
For example, the version "1.3.5" has a major version of 1, a minor version of 3, and an incremental version of 5. The version "5" has a major version of 5 and no minor or incremental version. The qualifier exists to capture milestone builds: alpha and beta releases, and the qualifier is separated from the major, minor, and incremental versions by a hyphen. For example, the version "1.3-beta-01" has a major version of 1, a minor version of 3, and a qualifier of "beta-01".
Keeping your version numbers aligned with this standard will become very important when you want to start using version ranges in your POMs. Version ranges, introduced in Section 9.4.3, “Dependency Version Ranges”, allow you to specify a dependency on a range of versions, and they are only supported because Maven has the ability to sort versions based on the version release number format introduced in this section.
If your version release number matches the format
<major>.<minor>.<incremental>-<qualifier>
then your versions will be compared properly; "1.2.3" will be evaluated
as a more recent build than "1.0.2", and the comparison will be made
using the numeric values of the major, minor, and incremental versions.
If your version release number does not fit the standard introduced in
this section, then your versions will be compared as strings; "1.0.1b"
will be compared to "1.2.0b" using a String comparison.
One gotcha for release version numbers is the ordering of the qualifiers. Take the version release number "1.2.3-alpha-2" and the number "1.2.3-alpha-10". Where the "alpha-10" build corresponds to the tenth alpha build and the "alpha-2" build corresponds to the second alpha build. Even though "alpha-10" should be considered more recent than "alpha-2", Maven is going to sort "alpha-10" before "alpha-2" due to a known issue in the way Maven handles version numbers.
Maven is supposed to treat the number after the qualifier as a build number. In other words, the qualifier should be "alpha", and the build number should be 2. Even though Maven has been designed to separate the build number from the qualifier, this parsing is currently broken. As a result, "alpha-2" and "alpha-10" are compared using a String comparison, and "alpha-10" comes before "alpha-2" alphabetically. To get around this limitation, you will need to left-pad your qualified build numbers. If you use "alpha-02" and "alpha-10" this problem will go away, and it will continue to work once Maven properly parses the version build number.
Maven versions can contain a string literal to signify that a project is currently under active development. If a version contains the string "SNAPSHOT", then Maven will expand this token to a date and time value converted to UTC when you install or release this component. For example, if your project has a version of "1.0-SNAPSHOT", and you deploy this project's artifacts to a Maven repository, Maven will expand this version to "1.0-20080207-230803-1" if you were to deploy a release at 11:08 PM on February, 7th 2008 UTC. In other words, when you deploy a snapshot, you are not making a release of a software component, you are releasing a snapshot of a component at a specific time.
Why would you use this? SNAPSHOT versions are used for projects under active development. If your project depends on a software component that is under active development, you can depend on a SNAPSHOT release, and Maven will periodically attempt to download the latest snapshot from a repository when you run a build. Similarly, if the next release of your system is going to have a version "1.4", your project would have a version "1.4-SNAPSHOT" version until it was formally released.
As a default setting, Maven will not check for SNAPSHOT releases
on remote repositories, to depend on SNAPSHOT releases, users must
explicitly enable the ability to download snapshots using a
repository or pluginRepository
element in the POM.
When releasing a project you should resolve all dependencies on SNAPSHOT versions to dependencies on released versions. If a project depends on a SNAPSHOT, it is not stable as the dependencies may change over time. Artifacts published to non-snapshot Maven repositories such as http://repo1.maven.org/maven2 cannot depend on SNAPSHOT versions, as Maven's Super POM has snapshot's disabled from the Central repository. SNAPSHOT versions are for development only.
When you depend on a plugin or a dependency, you can use the a version value of LATEST or RELEASE. LATEST refers to the latest released or snapshot version of a particular artifact, the most recently deployed artifact in a particular repository. RELEASE refers to the last non-snapshot release in the repository. In general, it is not a best practice to design software which depends on a non-specific version of an artifact. If you are developing software, you might want to use RELEASE or LATEST as a convenience so that you don't have to update version numbers when a new release of a third-party library is released. When you release software, you should always make sure that your project depends on specific versions to reduce the chances of your build or your project being affected by a software release not under your control. Use LATEST and RELEASE with caution, if at all.
Starting with Maven 2.0.9, Maven locks down the version numbers of common and core Maven plugins in the super POM to standardize a core set of Maven plugins for a particular version of Maven. This change was introduced to Maven 2.0.9 to bring stability and reproducibility to Maven builds. Before Maven 2.0.9, Maven would automatically update core Maven plugins using the LATEST version. This behavior led to a number of surprises when bugs was introduced into core plugins or functionality changed in a core plugin which subsequently broke a build. When Maven automatically updated core plugins, it was noted that there was little guarantee that a build would be reproducible as plugins could be updated whenever a new version was pushed to the central repository. Starting with Maven 2.0.9, Maven, essentially, ships with a core set of locked down plugin versions. Non-core plugins, or plugins without versions assigned in the Super POM will still use the LATEST version to retrieve a plugin artifact from the repository. It is for this reason that you should assign explicit version numbers to any custom or non-core plugins used in your build.
A POM can include references to properties preceded by a dollar sign and surrounded by two curly braces. For example, consider the following POM.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<finalName>${project.groupId}-${project.artifactId}</finalName>
</build>
</project>If you put this XML in a
pom.xml and run mvn
help:effective-pom, you will see that the output contains the
line:
... <finalName>org.sonatype.mavenbook-project-a</finalName> ...
When Maven reads a POM, it replaces references
to properties when it loads the POM
XML. Maven properties occur frequently in advanced
Maven usage, and are similar to properties in other systems such as Ant
or Velocity. They are simply variables delimited by
${...}. Maven provides three implicit variables which
can be used to access environment variables, POM
information, and Maven Settings:
The env variable exposes environment
variables exposed by either your operating system or shell. For
example, a reference to ${env.PATH} in a Maven
POM would be replaced by the
${PATH} environment variable (or
%PATH% in Windows).
The project variable exposes the
POM. You can use a dot (.) notated path to
reference the value of a POM element. For
example, in this section we used the groupId
and artifactId to set the
finalName element in the build configuration.
The syntax for this property reference was:
${project.groupId}-${project.artifactId}.
The settings variable exposes Maven settings information.
You can use a dot (.) notated path to reference the value of an
element in a settings.xml file. For example,
${settings.offline} would reference the value
of the offline element in
~/.m2/settings.xml.
You may see older builds that use ${pom.xxx}
or just ${xxx} to reference POM
properties. These methods have been deprecated and only
${project.xxx} should be used.
In addition to the three implicit variables, you can reference system properties and any custom properties set in the Maven POM or in a build profile.
All properties accessible via
getProperties() on
java.lang.System are exposed as
POM properties. Some examples of system
properties are: ${user.name},
${user.home}, ${java.home},
and ${os.name}. A full list of system
properties can be found in the Javadoc for the
java.lang.System class.
Arbitrary properties can be set with a
properties element in a
pom.xml or settings.xml,
or properties can be loaded from external files. If you set a
property named fooBar in your
pom.xml, that same property is referenced
with ${fooBar}. Custom properties come in handy
when you are building a system that filters resources and targets
different deployment platforms. The syntax for setting ${foo}=bar
in a pom is shown below.
<properties> <foo>bar</foo> </properties>
For a more comprehensive list of available properties, see Chapter 13, Properties and Resource Filtering.
Maven can manage both internal and external dependencies. An external dependency for a Java project might be a library such as Plexus, the Spring Framework, or Log4J. An internal dependency is illustrated by a web application project depending on another project which contains service classes, model objects, or persistence logic. Example 9.3, “Project Dependencies” shows some examples of project dependencies.
Example 9.3. Project Dependencies
<project>
...
<dependencies>
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-java5</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-servlet_2.4_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
</project>
The first dependency is a compile dependency on the XFire SOAP library from Codehaus. You would use this type of dependency if your project depended on this library for compilation, testing, and during execution. The second dependency is a test scoped dependency on JUnit. You would use a test-scoped dependency when you only need to reference this library during testing. The last dependency in Example 9.3, “Project Dependencies” is a dependency on the Servlet 2.4 API as implemented by the Apache Geronimo project. The last dependency is scoped as a provided dependency. You would use a provided scope when the application you are developing needs a library for compilation and testing, but this library is supplied by a container at runtime.
Example 9.3, “Project Dependencies” briefly introduced three of the five dependency scopes: compile, test, and provided. Scope controls which dependencies are available in which classpath, and which dependencies are included with an application. Let's explore each scope in detail:
Compile is the default scope; all dependencies are compile scoped if a scope is not supplied. Compile dependencies are available in all classpaths and they are packaged.
Provided dependencies are used when you expect the JDK or a container to provide them. For example, if you were developing a web application, you would need the Servlet API available on the compile classpath to compile a servlet, but you wouldn't want to include the Servlet API in the packaged WAR; the Servlet API JAR is supplied by your application server or servlet container. Provided dependencies are available on the compilation classpath (not runtime), provided dependencies are not transitive, nor are they packaged.
Runtime dependencies are required to execute and test the system, but they are not required for compilation. For example, you may need a JDBC API JAR at compile time and the JDBC driver implementation only at runtime.
Test scoped dependencies are not required during the normal operation of an application, and are only available during test compilation and execution phases. The test scope was previously introduced in Section 4.10, “Adding Test-scoped Dependencies”.
This scope is similar to provided except that you have to
provide an explicit path to the JAR on the
local file system. This is intended to allow compilation against
native objects that may be part of the system libraries. The
artifact is assumed to always be available and is not looked up in
a repository. If you declare the scope to be system you must also
provide the systemPath element. Note that this
scope is not recommended (you should always try to reference
dependencies in a public or custom Maven repository).
Assume that you were working on a library which provides caching behavior. Instead of writing a caching system from scratch you want to use some of the existing libraries which provide caching on the file system and distributed caches. Also assume that you want to give the end user an option to cache on the file system or to use an in-memory distributed cache. To cache on the file system you'll want to use a freely available library called EHCache, and to cache in a distributed in-memory cache, you use another freely available caching library named SwarmCache. You'll code an interface and create a library that can be configured to use either EHCache or SwarmCache, but you want to avoid adding a dependency on both caching libraries to any project that depends on your library.
In other words, you need both libraries to compile this library project, but you don't want both libraries to show up as transitive runtime dependencies for the project that uses your library. You can accomplish this by using optional dependencies as shown in Example 9.4, “Declaring Optional Dependencies”.
Example 9.4. Declaring Optional Dependencies
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.4.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>swarmcache</groupId>
<artifactId>swarmcache</artifactId>
<version>1.0RC2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
</dependency>
</dependencies>
</project>Once you've declared these dependencies as optional, you are
required to include them explicitly in the project that depends on
my-project. For example, if you were writing an
application which depended on my-project and wanted
to use the EHCache implementation, you would need to add the following
dependency element to your project.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-application</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>swarmcache</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
</project>In an ideal world, you wouldn't have to use optional dependencies.
Instead of having one large project with a series of optional
dependencies you would separate the EHCache-specific code to a
my-project-ehcache submodule and the
SwarmCache-specific code to a my-project-swarmcache
submodule. This way, instead of requiring projects that reference
my-project to specifically add a dependency, projects
could just reference a particular implementation project and benefit
from the transitive dependency.
You don't just have to depend on a specific version of a dependency, you can specify a range of versions that would satisfy a given dependency. For example, you can specify that your project depends on version 3.8 or greater of JUnit, or anything between version 1.2.10 and 1.2.14 of Log4J. You do this by surrounding one or more version numbers with the following characters:
(, ) - exclusive quantifiers
[, ] - inclusive quantifiers
For example, if you wished to access any JUnit
version greater than or equal to 3.8, but less than 4.0, your dependency
would be thus:
Example 9.5. Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[3.8,4.0)</version> <scope>test</scope> </dependency>
If you want to depend on any version of JUnit no higher than 3.8.1, you would only specify an upper inclusive boundary.
Example 9.6. Specifying a Dependency Range: JUnit <= 3.8.1
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[,3.8.1]</version> <scope>test</scope> </dependency>
A version before or after the comma is not required, and means +/- infinity. For example, "[4.0,)" means any version greater than or equal to 4.0. "(,2.0)" is any version less than 2.0. "[1.2]" means only version 1.2, and nothing else.
When declaring a "normal" version such as 3.8.2 for Junit,
internally this is represented as "allow anything, but prefer 3.8.2."
This means that when a conflict is detected, Maven is allowed to use
the conflict algorithms to choose the best version. If you specify
[3.8.2], it means that only 3.8.2 will be used and nothing else. If
somewhere else there is a dependency that specifies [3.8.1], you would
get a build failure telling you of the conflict. We point this out to
make you aware of the option, but use it sparingly and only when
really needed. The preferred way to resolve this is via
dependencyManagement.
A transitive dependency is a dependency of a dependency. If
project-a depends on project-b
which, in turn, depends on project-c, then
project-c is considered a transitive dependency of
project-a. If project-c depended
on project-d, then project-d would
also be considered a transitive dependency of
project-a. Part of Maven's appeal is that it can
manage transitive dependencies and shield the developer from having to
keep track of all of the dependencies required to compile and run an
application. You can just depend on something like the Spring Framework,
and not have to worry about tracking down every last dependency of the
Spring Framework.
Maven accomplishes this by building a graph of dependencies and
dealing with any conflicts and overlaps which might occur. For example,
if Maven sees that two projects depend on the same
groupId and artifactId, it will
sort out which dependency to use automatically, always favoring the more
recent version of a dependency. While this sounds convenient, there are
some edge cases where transitive dependencies can cause some
configuration issues. For these scenarios, you can use a dependency
exclusion.
Each of the scopes outlined in Section 9.4.1, “Dependency Scope” affects more than just the scope of the dependency in the declaring project, but also how it acts as a transitive dependency. The easiest way to convey this information is through a table. Scopes in the top row represent the scope of a transitive dependency. Scopes in the left-most column represent the scope of a direct dependency. The intersection of the row and column is the scope that is assigned to a transitive dependency. A blank cell in this table means that the transitive dependency will be omitted.
Table 9.1. How Scope Affects Transitive Dependencies
| Direct Scope | Transitive Scope | |||
|---|---|---|---|---|
| compile | provided | runtime | test | |
| compile | compile | - | runtime | - |
| provided | provided | provided | provided | - |
| runtime | runtime | - | runtime | - |
| test | test | - | test | - |
To illustrate the relationship of transitive dependency scope to
direct dependency scope, consider the following example. If
project-a contains a test scoped dependency on
project-b which contains a compile scoped
dependency on project-c.
project-c would be a test-scoped transitive
dependency of project-a.
You can think of this as a transitive boundary which acts as a filter on dependency scope. Transitive dependencies which are provided and test scope usually do not affect a project. The exception to this rule is that a provided scoped transitive dependency to a provided scope direct dependency is still a provided dependency of a project. Transitive dependencies which are compile and runtime scoped usually affect a project regardless of the scope of a direct dependency. Transitive dependencies which are compile scoped will have the same scope irregardless of the scope of the direct dependency. Transitive dependencies which are runtime scoped will generally have the same scope of the direct dependency except when the direct dependency has a scope of compile. When a transitive dependency is runtime scoped and a direct is compile scoped the direct dependency the transitive dependency will have an effective scope of runtime.
There are times when you will need to exclude a transitive
dependency. When you are depending on a project which depends on another
project but you would like to either exclude the dependency altogether
or replace the transitive dependency with another dependency which
provides the same functionality. Example 9.7, “Excluding a Transitive Dependency” shows an
example of a dependency element which adds a dependency on
project-a but excludes the transitive dependency
project-b.
Example 9.7. Excluding a Transitive Dependency
<dependency>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>project-b</artifactId>
</exclusion>
</exclusions>
</dependency>Often you will want to replace a transitive dependency with another implementation. For example, if you are depending on a library which depends on the Sun JTA API, you may want to replace the declared transitive dependency. Hibernate is one example, Hibernate depends on the Sun JTA API JAR which is not available in the central Maven repository as it can not be freely redistributed. Fortunately, the Apache Geronimo project has created an independent implementation of this library which can be freely redistributed. To replace a transitive dependency with another dependency, you would exclude the transitive dependency and declare a dependency on the project you wanted instead. Example 9.8, “Excluding and Replacing a Transitive Dependency” shows an example of a such replacement.
Example 9.8. Excluding and Replacing a Transitive Dependency
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.5.ga</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jta_1.1_spec</artifactId>
<version>1.1</version>
</dependency>
</dependencies>In Example 9.8, “Excluding and Replacing a Transitive Dependency”, there is nothing marking
the dependency on geronimo-jta_1.1_spec as a
replacement, it just happens to be a library which provides the same
API as the original JTA
dependency. Here are some other reasons you might want to exclude or
replace transitive dependencies:
The groupId or
artifactId of the artifact has changed, where the
current project requires an alternately named version from a
dependency's version - resulting in 2 copies of the same project in
the classpath. Normally Maven would capture this conflict and use a
single version of the project, but when groupId
or artifactId are different, Maven will consider
this to be two different libraries.
An artifact is not used in your project and the transitive dependency has not been marked as an optional dependency. In this case, you might want to exclude a dependency because it isn't something your system needs and you are trying to cut down on the number of libraries distributed with an application.
An artifact which is provided by your runtime container thus
should not be included with your build. An example of this is if a
dependency depends on something like the Servlet API and you want to
make sure that the dependency is not included in a web application's
WEB-INF/lib directory.
To exclude a dependency which might be an API with multiple implementations. This is the situation illustrated by Example 9.8, “Excluding and Replacing a Transitive Dependency”; there is a Sun API which requires click-wrap licensing and a time-consuming manual install into a custom repository (Sun's JTA JAR) versus a freely distributed version of the same API available in the central Maven repository (Geronimo's JTA implementation).
Once you've adopted Maven at your super complex enterprise and you
have two hundred and twenty inter-related Maven projects, you are going
to start wondering if there is a better way to get a handle on
dependency versions. If every single project that uses a dependency like
the MySQL Java connector needs to independently list the version number
of the dependency, you are going to run into problems when you need to
upgrade to a new version. Because the version numbers are distributed
throughout your project tree, you are going to have to manually edit
each of the pom.xml files that reference a
dependency to make sure that you are changing the version number
everywhere. Even with find, xargs,
and awk, you are still running the risk of missing a
single POM.
Luckily, Maven provides a way for you to consolidate dependency
version numbers in the dependencyManagement element.
You'll usually see the dependencyManagement element
in a top-level parent POM for an organization or
project. Using the dependencyManagement element in a
pom.xml allows you to reference a dependency in a
child project without having to explicitly list the version. Maven will
walk up the parent-child hierarchy until it finds a project with a
dependencyManagement element, it will then use the
version specified in this dependencyManagement
element.
For example, if