Search code examples
mavenpom.xmlartifacttransitive-dependency

How to debug artifact replacement in Maven


I have a parent project contains a dozen child projects, one of the child projects use org.apache.httpcomponents:httpclient:jar:4.3.5, which depends on org.apache.httpcomponents:httpcore:jar:4.3.2.

However, the result version of httpcore is resolved to 4.2.1 instead of 4.3.2.

The following is an extraction of the output when running dependency:tree with debug option checked in Eclipse:

...
[DEBUG] Using mirror nexus (http://192.168.0.111:8081/nexus/content/groups/public) for apache.snapshots (http://repository.apache.org/snapshots).
[DEBUG]   testArtifact: artifact=org.apache.httpcomponents:httpclient:jar:4.3.5:compile
[DEBUG]   includeArtifact: artifact=org.apache.httpcomponents:httpclient:jar:4.3.5:compile
[DEBUG]   startProcessChildren: artifact=org.apache.httpcomponents:httpclient:jar:4.3.5:compile
[DEBUG]     manageArtifactVersion: artifact=org.apache.httpcomponents:httpcore:jar:4.3.2:compile, replacement=org.apache.httpcomponents:httpcore:jar:4.2.1
[DEBUG] Using mirror nexus (http://192.168.0.111:8081/nexus/content/groups/public) for apache.snapshots (http://repository.apache.org/snapshots).
...

It just shows replacement=org.apache.httpcomponents:httpcore:jar:4.2.1, but it tells nothing about the reason of the replacement. The parent project's pom.xml uses quite a lot dependencies and even though I could try to remove those dependencies one by one and check the result, it would be quite time consuming. Is there any more effective way to debug the artifact replacement?


Here is almost the full log of the dependency:tree from Eclipse with debug option checked.


Solution

  • From your log, you can find the lines:

    [DEBUG] com.company.xyz:xyz-integration-lib:jar:0.0.1-SNAPSHOT
    [DEBUG]    com.company.xyz:xyz-utils:jar:0.0.1-SNAPSHOT:compile
    [DEBUG]       commons-codec:commons-codec:jar:1.8:compile
    [DEBUG]    javax.mail:mail:jar:1.4:provided
    [DEBUG]       javax.activation:activation:jar:1.1.1:provided (version managed from 1.1 by org.jboss.spec:jboss-javaee-6.0:3.0.2.Final)
    [DEBUG]    org.apache.commons:commons-lang3:jar:3.3.2:compile
    [DEBUG]    junit:junit:jar:4.8.2:test
    [DEBUG]    com.thoughtworks.xstream:xstream:jar:1.4.7:compile
    [DEBUG]       xmlpull:xmlpull:jar:1.1.3.1:compile
    [DEBUG]       xpp3:xpp3_min:jar:1.1.4c:compile
    [DEBUG]    joda-time:joda-time:jar:2.4:compile
    [DEBUG]    org.assertj:assertj-joda-time:jar:1.1.0:test
    [DEBUG]       org.assertj:assertj-core:jar:1.3.0:test
    [DEBUG]    org.apache.httpcomponents:httpclient:jar:4.3.5:compile
    [DEBUG]       org.apache.httpcomponents:httpcore:jar:4.2.1:compile (version managed from 4.3.2 by org.jboss.as:jboss-as-parent:7.2.0.Final)
    [DEBUG]    commons-logging:commons-logging:jar:1.1.3:compile
    [DEBUG]    org.slf4j:slf4j-api:jar:1.7.7:compile
    [DEBUG]    org.slf4j:slf4j-log4j12:jar:1.7.7:compile
    [DEBUG]       log4j:log4j:jar:1.2.17:compile
    [DEBUG]    org.mockito:mockito-all:jar:1.9.5:test
    [DEBUG]    org.powermock:powermock-module-junit4:jar:1.5.5:test
    [DEBUG]       org.powermock:powermock-module-junit4-common:jar:1.5.5:test
    [DEBUG]          org.powermock:powermock-core:jar:1.5.5:test
    [DEBUG]             org.javassist:javassist:jar:3.18.1-GA:test (version managed from 3.18.2-GA by org.springframework.boot:spring-boot-dependencies:1.1.4.RELEASE)
    [DEBUG]          org.powermock:powermock-reflect:jar:1.5.5:test
    [DEBUG]             org.objenesis:objenesis:jar:2.1:test
    [DEBUG]    org.powermock:powermock-api-mockito:jar:1.5.5:test
    [DEBUG]       org.powermock:powermock-api-support:jar:1.5.5:test
    

    Where you can see that the javassist and httpcore versions are dropped by certain transitive dependencies and javax.activation version is raised by one.

    This happens when more than one of your project dependencies are depending on the same library and have defined dependencies to different versions of that library. This can be annoying, since generally you cannot change how the parent POM or its dependencies are affecting the versions of your transitive dependencies.

    The mediation rules from Maven docs are as follows:

    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.

    "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. You could explicitly add a dependency to D 2.0 in A to force the use of D 2.0

    However what you can do is manage the dependency versions yourself. This is called dependency management and as stated by the same docs:

    Dependency management - this allows project authors to directly specify the versions of artifacts to be used when they are encountered in transitive dependencies or in dependencies where no version has been specified. In the example in the preceding section a dependency was directly added to A even though it is not directly used by A. Instead, A can include D as a dependency in its dependencyManagement section and directly control which version of D is used when, or if, it is ever referenced.

    Thus, you can just add:

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>bar</groupId>
          <artifactId>foo</artifactId>
          <version>1.2.3</version>
        </dependency>
      </dependencies>
    </dependencyManagement>
    

    into your own POM and this will always override whatever version are being defined for your transitive dependencies through dependency mediation.