Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Maven Choose the Version of a Transitive Dependency when Two or More Versions of that Dependency Exist in the Dependency Tree?

I've got a project that depends on a library that I maintain called microservices-common. The microservices-common library in turn depends on commons-codec:1.11. However, when I attempt to use microservices-common in my project, commons-codec:1.10 ends up on my classpath, and my code fails to compile, because microservices-common is attempting to use a org.apache.commons.codec.digest.DigestUtils constructor that was added to commons-codec:1.11, but isn't present in commons-codec:1.10.

Here's the relevant portion of the dependency tree for microservices-common:

[INFO] com.myproject:microservice-common:jar:1.0-SNAPSHOT
[INFO] +- commons-codec:commons-codec:jar:1.11:compile
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.5.5:compile
[INFO] |  \- (commons-codec:commons-codec:jar:1.10:compile - omitted for conflict with 1.11)
[INFO] \- com.myproject:restful:jar:4.1.5-SNAPSHOT:compile
[INFO]    +- com.myproject:restful-common:jar:4.1.5-SNAPSHOT:compile
[INFO]    |  \- (commons-codec:commons-codec:jar:1.8:compile - omitted for conflict with 1.11)
[INFO]    \- (commons-codec:commons-codec:jar:1.8:compile - omitted for conflict with 1.11)

If I'm reading the tree correctly, other versions of the commons-codec dependency, including v1.8 and v1.10 are being ommitted from the classpath in favour of v1.11, which is what I want.

However, if I pull the dependency tree from the perspective of my project that depends on microservices-common, it looks like this:

[INFO] com.myproject:microservice:jar:1.0-SNAPSHOT
[INFO] +- org.apache.httpcomponents:httpasyncclient:jar:4.1.3:compile
[INFO] |  \- org.apache.httpcomponents:httpclient:jar:4.5.3:compile
[INFO] |     \- (commons-codec:commons-codec:jar:1.10:compile - version managed from 1.11; omitted for duplicate)
[INFO] \- com.myproject:microservice-common:jar:1.0-SNAPSHOT:compile
[INFO]    +- commons-codec:commons-codec:jar:1.10:compile
[INFO]    \- com.myproject:restful:jar:4.1.5-SNAPSHOT:compile
[INFO]       +- com.myproject:restful-common:jar:4.1.5-SNAPSHOT:compile
[INFO]       |  \- (commons-codec:commons-codec:jar:1.10:compile - version managed from 1.8; omitted for duplicate)
[INFO]       \- (commons-codec:commons-codec:jar:1.10:compile - version managed from 1.8; omitted for duplicate)

In this tree, I'm seeing the message "version managed from 1.x; omitted for duplicate". I'm not sure what that means exactly, and, more concerning, line 6 shows that commons-codec:1.10 is what's ending up on my classpath, rather than v1.11, which is what I actually want.

It should be noted that the pom.xml for com.myproject:microservice-common:jar:1.0-SNAPSHOT declares the commons-codec:1.11 dependency, so the only places that the commons-codec:1.10 could have come from is either org.apache.httpcomponents:httpclient:jar:4.1.3 or com.myproject:restful:jar:4.1.5-SNAPSHOT (another common library that I can't get rid of for legacy reasons), but it's not clear to me why that version of the transitive dependency is being selected for inclusion over the version that my microservices-common library declares.

Can anybody explain how dependency selection works when multiple versions of the same library exist in the dependency tree, and why microservices-common appears to select the correct version of the transitive dependency when built in isolation, but my microservices project selects a different version when I build it?

like image 516
MusikPolice Avatar asked May 09 '18 21:05

MusikPolice


1 Answers

Maven chooses the version of the dependency that is nearest in the dependency tree. This is explained very well in the Maven documentation:

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

If the dependency is occurring multiple times at the same level, the first declaration wins (since Maven 2.0.9).

The best and established way to ensure using the required version of commons-codec, is to declare dependencyManagement in your 'microservice' pom (directly under the project element):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Also, make sure you run a recent version of Maven (3.5 recommended).

like image 189
gjoranv Avatar answered Oct 08 '22 18:10

gjoranv