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.

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.
Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

4 Responses to "When Maven Dependency Plugin Lies"

  1. Keith Sader says:

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

  2. Sean Flanigan says:

    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. Neo Lite says:

    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


3 × = eighteen



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

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

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close