Maven: The Complete Reference
3.4. Project Dependencies

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 that contains service classes, model objects, or persistence logic. Project Dependencies shows some examples of 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>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</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 need to
reference this library only during testing. The last dependency in
Project Dependencies is a dependency on the Servlet 2.4 API. 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.
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
-
compile+ is the default scope; all dependencies are
compile-scoped if a scope is not supplied.compiledependencies are available in all classpaths, and they are packaged. - provided
-
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.
provideddependencies are available on the compilation classpath (not runtime). They are not transitive, nor are they packaged. - runtime
- 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
- test+-scoped dependencies are not required during the normal operation of an application, and they are available only during test compilation and execution phases.
- system
-
The
systemscope is similar toprovidedexcept 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 besystem, you must also provide thesystemPathelement. Note that this scope is not recommended (you should always try to reference dependencies in a public or custom Maven repository).
Assume that you are working on a library that provides caching behavior. Instead of writing a caching system from scratch, you want to use some of the existing libraries that 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 (http://ehcache.sourceforge.net/), and to cache in a distributed in-memory cache, you want to use another freely available caching library named SwarmCache ( http://swarmcache.sourceforge.net/ ). 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 Declaring Optional Dependencies.
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>
Since you’ve declared these dependencies as optional in my-project,
if you’ve defined a project that depends on my-project which needs
those dependencies, you’ll have 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 can just reference a particular implementation project and
benefit from the transitive dependency.
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 versions 1.2.10 and 1.2.14 of JUnit. 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 as shown
in Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0.
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 specify only an upper inclusive boundary, as shown in Specifying a Dependency Range: JUnit ⇐ 3.8.1.
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.
Note
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.
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 that 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. Although 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 earlier in the section Section 3.4.1, “Dependency Scope” affects not 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, as in Table 3.1, “How Scope Affects Transitive Dependencies”. Scopes in the top row represent the scope of a transitive dependency. Scopes in the leftmost 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 3.1. How Scope Affects Transitive Dependencies
| Direct Scope | vs. Transitive Scope | |||
|---|---|---|---|---|
|
compile |
provided |
runtime |
test |
|
|
compile |
compile |
- |
runtime |
- |
|
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. 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 regardless 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 will be times when you need to exclude a transitive dependency,
such as when you are depending on a project that depends on another
project, but you would like to either exclude the dependency
altogether or replace the transitive dependency with another
dependency that provides the same functionality. Excluding a Transitive Dependency shows
an example of a dependency element that adds a dependency on
project-a, but excludes the transitive dependency project-b.
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 that 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 because it cannot be freely redistributed. Fortunately, the Apache Geronimo project has created an independent implementation of this library that 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. Excluding and Replacing a Transitive Dependency shows an example of a such replacement.
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 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
groupIdorartifactIdof 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 whengroupIdorartifactIdare 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 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). ==== Dependency Management
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 you have a large set of projects which make use of the
MySQL Java connector version 5.1.2, you could define the following
dependencyManagement element in your multi-module project’s
top-level POM.
Defining Dependency Versions in a Top-level POM.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.2</version>
<scope>runtime</scope>
</dependency>
...
<dependencies>
</dependencyManagement>
Then, in a child project, you can add a dependency to the MySQL Java Connector using the following dependency XML:
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>project-a</artifactId>
...
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
You should notice that the child project did not have to explicitly
list the version of the mysql-connector-java dependency. Because
this dependency was defined in the top-level POM’s
dependencyManagement element, the version number is going to
propagate to the child project’s dependency on
mysql-connector-java. Note that if this child project did define a
version, it would override the version listed in the top-level POM’s
dependencyManagement section. That is, the dependencyManagement
version is only used when the child does not declare a version
directly.
Dependency management in a top-level POM is different from just
defining a dependency on a widely shared parent POM. For starters, all
dependencies are inherited. If mysql-connector-java were listed as a
dependency of the top-level parent project, every single project in
the hierarchy would have a reference to this dependency. Instead of
adding in unnecessary dependencies, using dependencyManagement
allows you to consolidate and centralize the management of dependency
versions without adding dependencies which are inherited by all
children. In other words, the dependencyManagement element is
equivalent to an environment variable which allows you to declare a
dependency anywhere below a project without specifying a version
number.
