4

in my opinion the maven dependency plugin is misbehaving when calculating the dependency list.

Assume these 3 projects:

base1:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                            
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>mygroup</groupId>
    <artifactId>base1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
      <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.3</version>
      </dependency>
    </dependencies>
</project>

base2:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                            
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>mygroup</groupId>
    <artifactId>base2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
      <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
      </dependency>
    </dependencies>
</project>

combined:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                            
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>mygroup</groupId>
    <artifactId>combined</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
      <dependency>
        <groupId>mygroup</groupId>
        <artifactId>base1</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>mygroup</groupId>
        <artifactId>base2</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
    </dependencies>
</project>

Both, base1 and base2 depend on commons-lang, but each on a different version! combined depends on both, base1 and base2.

When calling mvn dependency:list on combined, I would expect to see base1, base2 and commons-lang in versions 2.3 and 2.6, since both are used. However the actual output is:

[INFO] The following files have been resolved:
[INFO]    commons-lang:commons-lang:jar:2.3:compile
[INFO]    mygroup:base1:jar:1.0-SNAPSHOT:compile
[INFO]    mygroup:base2:jar:1.0-SNAPSHOT:compile

It is not even using the common-lang with the highest version number, but just the one it finds first.

How can I avoid this? I need all dependencies.

radlan
  • 2,393
  • 4
  • 33
  • 53
  • The best for you would be to change the code that requires a dependency on commons-lang:2.3. Sorry... – Olivier Tonglet Feb 09 '17 at 15:50
  • @otonglet If base1 and base2 are third-party modules, I can't do that. – radlan Feb 09 '17 at 15:59
  • @radlan are you by any chance confusing `mvn dependency:list` with [`mvn dependency:tree`](https://maven.apache.org/plugins/maven-dependency-plugin/tree-mojo.html) ? – Naman Feb 09 '17 at 16:10
  • @nullpointer No, I am talking more about the actual algorithm used for calculating the dependencies. `mvn dependency:copy-dependencies` is what is actually used. But since that is the same as `mvn dependency:list` I mentioned this, because it is easier to put into an example. – radlan Feb 10 '17 at 11:18

3 Answers3

4

According to this official documentation (with the relevant part highlighted in bold):

Dependency mediation - this determines what version of a dependency will be used when multiple versions of an artifact are encountered. Currently, Maven 2.0 only supports using the "nearest definition" which means that it will use the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, until Maven 2.0.8 it was not defined which one would win, but since Maven 2.0.9 it's the order in the declaration that counts: the first declaration wins.

Therefore, Maven picks version 2.3 because it is encountered first in the dependency resolution process. Note that if you run mvn dependency:tree on the combined module, it will show which version was used and which one was omitted.

The best solution is to explicitly pick the version you want in the combined artifact, by declaring the dependency in its POM so that Maven favors it over other versions:

<?xml version="1.0" encoding="UTF-8"?>
<project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>mygroup</groupId>
   <artifactId>combined</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>jar</packaging>

   <dependencies>
     <dependency>
       <groupId>mygroup</groupId>
       <artifactId>base1</artifactId>
       <version>1.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>mygroup</groupId>
       <artifactId>base2</artifactId>
       <version>1.0-SNAPSHOT</version>
     </dependency>
     <dependency>    <!-- This will override the versions in base1 and base2 -->
       <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
       <version>2.6</version>
     </dependency>
   </dependencies>

Note that Maven cannot pick two versions because in this case there would be two definitions for the same classes on your project's classpath, which can lead to unexpected issues at runtime.

M A
  • 71,713
  • 13
  • 134
  • 174
  • Actually that seems to be the only solution. It is not exactly what I want, since for example base1 and base2 are executable applications whose classpath is generated on build time and combined is just a _container_ for those applications that assembles all dependencies, base2 will not be runnable, since the dependency of its classpath is not contained in the assembled dependencies. However, it seems that I am stuck here and your proposal would be the best solution possible. – radlan Feb 09 '17 at 16:07
  • @radlan I may be missing something but based on your description where you say "base1 and base2 are executable applications", it seems you should separate the build into two separate builds, where build #1 builds base1 and build #2 builds base2, and in this case each build uses its own separate version of the dependency. Anyway here is a post that seems related: http://stackoverflow.com/questions/24962607/multiple-versions-of-the-same-dependency-in-maven. I happen to have answered it as well. You may be able to use Maven profiles in this case. Each profile can declare its own dependency. – M A Feb 09 '17 at 17:08
  • base1 and base2 have separate builds. But there is also another build, combined, that bundles those other builds to ease deployment of the whole bundle. – radlan Feb 10 '17 at 11:20
1

Maven scans the pom from top to bottom and uses the first version it encounters.

Assuming you really need both version of commons-lang, you could put those two versions in your project and use maven to package them in your jar.

Yet, how could the compiler know if a call to StringUtils.isEmpty() calls the version 2.3 or 2.6 ?

Same discussion here.

Community
  • 1
  • 1
Olivier Tonglet
  • 3,312
  • 24
  • 40
1

Maven always resolves conflicts using "nearest wins" strategy. You can run the following command to see why a particular version is used:

mvn dependency:tree -Dverbose -Dincludes=commons-lang

See following for more info: https://maven.apache.org/plugins/maven-dependency-plugin/examples/resolving-conflicts-using-the-dependency-tree.html

Imran Saeed
  • 3,414
  • 1
  • 16
  • 27