Understanding How Maven Dependencies Work

Let’s assume we have the following dependencies:-

<dependencies>
    <dependency>
        <groupId>dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>3.2.1.RELEASE</version>
    </dependency>
</dependencies>

In this example, both dbunit and spring-core have a dependency on commons-logging, but they rely on a different version:-

dbunit      -> commons-logging v1.0.4
spring-core -> commons-logging v1.1.1

Based on the above configuration, what version of commons-logging does Maven choose?

If your answer is v1.1.1, then you are absolutely right…. that you need to read the Maven documentation again.

If you are using IntelliJ, you can easily create a “Dependencies” diagram that looks like this:-
maven-dependencies-1

If you click on commons-logging used by spring-core, there is an arrow pointing to commons-logging used by dbunit. In another word, v1.0.4 is used.

maven-dependencies-2

The Maven documentation says:-

... "nearest definition" means that the version used will be the closest one to your project in the tree of dependencies, eg. if dependencies for A, B, and C are defined as A -> B -> C -> D 2.0 and A -> E -> D 1.0, then D 1.0 will be used when building A because the path from A to D through E is shorter ...

Since the two dependency versions for commons-logging are at the same depth in the dependency tree, the order of the declaration becomes very important. The first declaration will be chosen and the rest will be ignored.

To proof that we are not on crack, we will swap the dependency order this time:-

<dependencies>
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-core</artifactId>
	    <version>3.2.1.RELEASE</version>
	</dependency>
    <dependency>
        <groupId>dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.2</version>
    </dependency>
</dependencies>

In this case, the arrow points from commons-logging used by dbunit to commons-logging used by spring-core. In another word, v1.1.1 is used.

maven-dependencies-3

To avoid any further confusion on which dependency version will be chosen by Maven, you can always explicitly define that dependency in pom.xml:-

<dependencies>
	<dependency>
	    <groupId>commons-logging</groupId>
	    <artifactId>commons-logging</artifactId>
	    <version>1.1.2</version>
	</dependency>
    <dependency>
        <groupId>dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>3.2.1.RELEASE</version>
    </dependency>
</dependencies>

In this case, we just force both dbunit and spring-core to use v1.1.2:-

maven-dependencies-4

maven-dependencies-5

RECOMMENDED SOLUTION

My recommended solution is to ALWAYS define the dependencies under dependencyManagement tag.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.2.1.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Don't specify the dependency version here -->
<dependencies>
    <dependency>
        <groupId>dbunit</groupId>
        <artifactId>dbunit</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
</dependencies>

Just because you have dependencies defined under dependencyManagement tag, it doesn’t get included into your project classpath automatically. All it is saying is SHOULD you need to use, say dependency A, you will get version X.

If you define all your dependencies under dependencyManagement tag, you generally don’t define the versions under dependencies tag unless you want to override it.

In the above example, notice that you don’t need to define commons-logging under dependencies tag, unless you need it to compile or run your code. That said, when you run mvn clean package on a WAR module, commons-logging will be included in the WEB-INF/lib.

If we generate the “Dependencies” diagram again, all dependencies on commons-logging will now point to v1.1.2. There is zero confusion and we don’t have to worry about our peers from wrecking the project when they add new dependencies in the wrong order or upgrade existing dependency versions that may introduce potential version conflicts.

maven-dependencies-6

maven-dependencies-7

Why Should You Always Use dependencyManagement?

Let’s assume your team works on a multi-module project that looks like this:-

myproject
|- ear module     <- for wrapping the war module
   |- pom.xml      
|- war module     <- web application
   |- pom.xml
|- jar module #1  <- for other teams to reuse the API
   |- pom.xml
|- jar module #2  <- used by cron job
   |- pom.xml
|- pom.xml        <- parent pom     

The main goal when working on a multi-module project is to ensure every module uses the same dependency version, if possible, to ensure there are no compatibility problems. The last thing you want is to have Team A building jar module #1 with Spring 3.x and Team B building jar module #2 with Spring 2.x.

In this situation, you should always consider defining all the dependencies under dependencyManagement tag in the parent pom and configure all child module poms to extend the parent pom. This simple configuration enforces dependency version standardization across all modules within the project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s