Home » Java » Enterprise Java » When Maven Dependency Plugin Lies

About Yoav Abrahami

Yoav is the Chief Architect at Wix.com, working with developers and operations to build the company's future products as well as accelerating and improving development processes. Prior to joining Wix, Yoav was an Architect at Amdocs Cramer OSS division. Yoav holds a MS in Physics and a BS in Computer Science from the Tel Aviv University.

When Maven Dependency Plugin Lies

Problem:

We had an integration test which creates a spring ClassPathXmlApplicationContext and while doing so the test blew up with a NoSuchMethodError. It turned out that we had conflicting versions of dependencies on Spring artifacts. This in itself is not an unusual problem – such problems are addressed with the maven dependency plugin using the verbose option. However, what do you do when the maven plugin is wrong?
 
 
 

Investigation:

We started to dig in and we found that the AbstractAutowireCapableBeanFactory in its getTypeForFactoryMethod method tries to access the GenericTypeResolver resolveReturnTypeForGeneric method and fails on a java.lang.NoSuchMethodError: org.springframework.core.GenericTypeResolver.resolveReturnTypeForGenericMethod(Ljava/lang/reflect/Method;.

Initial investigation and googling we found that the method was added in 3.2.0 while we’re supposed to be running with 3.1.1. Further investigation determined that spring-data-mongodb depends on spring framework in range [3.0.7-4) 1 and because maven takes the latest available version given that range 2 it tried to take 3.2.2.
Note that the above changes a bit given a conflict between an explicit version dependency and a range dependency but, IINM, when determining the dependencies of spring mongo there is no conflict.

The problem was further masked by two symptoms:

  1. We have other projects that use this pattern and have no problem- This was explained by the fact that the conflict-resolving mechanism of maven chooses the nearest version it finds by default 3 and since all other projects which need spring-data-mongodb depend on this project they were lucky enough to grab the 3.1.1 version and not 3.2.2
  2. dependency:tree shows it brings 3.1.1 while bringing 3.2.2- Since the stack trace showed other results I wrote a test which checks from which jar each of the above classes comes from and verified that indeed the AbstractAutowireCapableBeanFactory class arrives from spring-beans 3.2.2 and not 3.1.1 as “mvn dependency:tree” showed (a big thanks to http://bit.ly/10zD1iV for the code snippet of finding the jar of a class in runtime).

Maven dependency:tree output showing spring-beans:3.1.1 is used in the artifact

&gt:mvn dependency:tree -Dverbose -Dincludes=org.springframework
...
(omitted for clarity)
...
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ wix-feature-toggle-administration ---
[INFO] artifact org.springframework:spring-beans: checking for updates from central
[INFO] artifact org.springframework:spring-beans: checking for updates from snapshots
[INFO] artifact org.springframework:spring-expression: checking for updates from central
[INFO] artifact org.springframework:spring-expression: checking for updates from snapshots
[INFO] artifact org.springframework:spring-tx: checking for updates from central
[INFO] artifact org.springframework:spring-tx: checking for updates from snapshots
[INFO] com.wixpress.common:wix-feature-toggle-administration:jar:2.180.0-SNAPSHOT
...
[INFO] +- org.springframework.data:spring-data-mongodb:jar:1.0.1.RELEASE:compile
[INFO] |  +- org.springframework:spring-beans:jar:3.1.1.RELEASE:compile
[INFO] |  |  \- (org.springframework:spring-core:jar:3.2.2.RELEASE:compile - omitted for conflict with 3.1.1.RELEASE)
[INFO] |  +- org.springframework:spring-expression:jar:3.1.1.RELEASE:compile
[INFO] |  |  \- (org.springframework:spring-core:jar:3.2.2.RELEASE:compile - omitted for conflict with 3.1.1.RELEASE)
[INFO] |  \- org.springframework.data:spring-data-commons-core:jar:1.2.1.RELEASE:compile
[INFO] |     +- (org.springframework:spring-beans:jar:3.1.1.RELEASE:compile - omitted for duplicate)
[INFO] |     \- (org.springframework:spring-tx:jar:3.1.1.RELEASE:compile - omitted for duplicate)
[INFO] +- com.wixpress.common:wix-framework:jar:2.180.0-SNAPSHOT:compile
[INFO] |  +- org.springframework:spring-core:jar:3.1.1.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-asm:jar:3.1.1.RELEASE:compile
...
I've removed additional outputs for clarity. The additional outputs were all 3.1.1 and were further down the tree (so irrelevant due to maven conflict resolving mechanism)

Test which proves spring-beans:3.2.2 is used in the artifact (asserting what the jvm in the error was saying)

package com.wixpress.springVersionBug;


import org.junit.*;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.core.GenericTypeResolver;
import java.security.CodeSource;
import static org.hamcrest.Matchers.endsWith;

/**
 * @author ittaiz
 * @since 3/24/13
 */

public class SpringVersionTest {


    @Test
    public void verifySpringBeans311InClasspath(){
        verifyCorrectSpringVersionInClasspathFor(AbstractAutowireCapableBeanFactory.class,"spring-beans-3.1.1.RELEASE.jar");
    }
    @Test
    public void verifySpringCore311InClasspath(){
        verifyCorrectSpringVersionInClasspathFor(GenericTypeResolver.class,"spring-core-3.1.1.RELEASE.jar");
    }
    public void verifyCorrectSpringVersionInClasspathFor(Class springClass,String expectedJarFileName){
        CodeSource springClassCodeSource = springClass.getProtectionDomain().getCodeSource();
        Assert.assertNotNull("expecting "+expectedJarFileName+" to be loaded by non-system class loader",springClassCodeSource);
        Assert.assertThat(springClassCodeSource.getLocation().toString(),endsWith(expectedJarFileName));
    }
}

The reason spring-core artifact came in 3.1.1 when spring-beans came as 3.2.2 is that our framework explicitly depends on spring-core and this artifact explicitly depends on the framework. This means spring-core 3.1.1 from framework is 2 hops which is shorter than the 3.2.2 from spring-data-mongodb.

Solution:

Depend on spring-data-mongodb while excluding spring-beans like so:

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-mongodb</artifactId>
  <version>1.0.1.RELEASE</version>
  <exclusions>
    <exclusion>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
    </exclusion>
  </exclusions>
</dependency>

The Open question mark:

Why dependency:tree (in verbose mode) did not show that it brings spring-beans in 3.2.2 but in 3.1.1 while explicitly specifying that spring-core 3.2.2 was removed due to conflict? I chalk this up to a bug in the dependency plugin.

  1. http://repo1.maven.org/maven2/org/springframework/data/spring-data-mongodb-parent/1.0.1.RELEASE/spring-data-mongodb-parent-1.0.1.RELEASE.pom
  2. http://www.maestrodev.com/better-builds-with-maven/creating-applications-with-maven/resolving-dependency-conflicts-and-using-version-ranges/
  3. http://www.maestrodev.com/better-builds-with-maven/creating-applications-with-maven/resolving-dependency-conflicts-and-using-version-ranges/

 

Reference: When Maven Dependency Plugin Lies from our JCG partner Yoav Abrahami at the Wix IO blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

4 comments

  1. Thanks for this post, I’ve been battling dependency issues at my workplace at well.

  2. According to a comment in the original blog the bug was fixed in maven-dependency-plugin:2.5: http://wix.io/2013/04/25/maven-classpath-hell-nosuchmethoderror-when-building-a-spring-context/#comment-55

  3. It is tough to fix such issues unless you have helpers like this. Thanks for putting the post.

    Neo

    http://javawithneo.blogspot.com

Leave a Reply

Your email address will not be published. Required fields are marked *

*


nine − = 8

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Do you want to know how to develop your skillset and become a ...

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!
Get ready to Rock!
To download the books, please verify your email address by following the instructions found on the email we just sent you.

THANK YOU!

Close