Chapter 9. The Project Object Model

9.1. Introduction

Favicon

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.

9.2. The POM

Favicon

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 Project Object Model

Figure 9.1. The Project Object Model


The POM contains four-categories of description and configuration, they are:

General Project Information

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.

Build Settings

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.

Build Environment

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”.

POM Relationships

A project rarely stands alone, it depends on other projects, inherits POM settings from parent projects, defines its own coordinates, and may include submodules.

9.2.1. The Super POM

Favicon

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:

1

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 settings.xml file. Note that the default Super POM has disabled snapshot artifacts on the central Maven repository. If you need to use a snapshot repository, you will need to customize repository settings in your pom.xml or in your settings.xml. Settings and profiles are covered in Chapter 11, Build Profiles and Section A.1, “Quick Overview”.

2

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.

3

The build element sets the default values for directories in the Maven Standard Directory layout.

4

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.

The Super POM is always the base Parent

Figure 9.2. The Super POM is always the base Parent


9.2.2. The Simplest POM

Favicon

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.

9.2.3. The Effective POM

Favicon

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-pom

Executing 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”.

9.2.4. Real POMs

Favicon

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.

9.3. POM Syntax

Favicon

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.

9.3.1. Project Versions

Favicon

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.

9.3.1.1. Version Build Numbers

Favicon

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.

9.3.1.2. SNAPSHOT Versions

Favicon

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.

9.3.1.3. LATEST and RELEASE Versions

Favicon

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.

9.3.2. Property References

Favicon

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:

env

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).

project

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}.

settings

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.

Note

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.

Java System Properties

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.

x

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.

9.4. Project Dependencies

Favicon

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.

9.4.1. Dependency Scope

Favicon

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

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

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

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 are only available during test compilation and execution phases. The test scope was previously introduced in Section 4.10, “Adding Test-scoped Dependencies”.

system

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).

9.4.2. Optional Dependencies

Favicon

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.

9.4.3. Dependency Version Ranges

Favicon

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.

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.

9.4.4. Transitive Dependencies

Favicon

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.

9.4.4.1. Transitive Dependencies and Scope

Favicon

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.

9.4.5. Conflict Resolution

Favicon

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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).

9.4.6. Dependency Management

Favicon

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