Maven supports JAR Hell

In one of my previous post I wrote that using development frameworks pays off and it is generally worth investing your time in learning how to use them. I was also clear that learning process is not easy and you could lose some hair from your head or simply go mad.

I would like to present an example how easily you can get lost using Spring Framework together with Struts 2 and Maven. The problem I’m going to present is often referred as a JAR Hell. I will present ideas how to cope with that.

The Problem

In the following paragraphs I’m going to present libraries dependency problems I encountered integrating Spring Framework with Struts2. I will also present how I discovered what the real problem is and how I solved it.

Let’s go
In one of my projects I use Spring integration with Struts2. In order to use this integration I have to add following dependency to pom.xml:


org.apache.struts
struts2-spring-plugin
${strutsVersion}


Spring dependency
I used not only Spring integration for Struts2 but Spring Framework itself thus I had to add dependency to it. If you want to use Spring you can add it to your project in two different ways. One way is to add only those Spring components you need – the full list is available here:


org.springframework
spring-beans
${springVersion}


org.springframework
spring-context
${springVersion}

...

The other way
This was the way I used it at the beginning but later I decided not to trash my POM and to replace multiple dependency entries with one (full Spring package with all components – it’s about 2.8 MB):


org.springframework
spring
${springVersion}

Here is my full pom.xml I’m using in the example.

JUnit errors
I built the project and my tests were OK – I was happy. Unfortunately when I started developing next feature I created new Spring-based unit test i.e. (please refer to Spring documentation for details):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:/spring/applicationContext*.xml",
"classpath:/spring/context-hibernate-test.xml"
})
public class ...Test {

and tried to run it from Eclipse. How surprised I was when I got this exception:

java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass(Ljava/lang/Class;Ljava/lang/Class;)

That’s pretty strange one 🙂 And the whole build process was OK when launched by Maven!

Investigation
I checked the Spring Forum and I found what causes this problem: So basically, just make sure you are using spring.jar and spring-test.jar from 2.5 RC1, and that should do the trick.
But Hey! I’m using Spring 2.5.5! WTF? After some brainstorming with myself I checked my project’s build path and I found something strange – in my classpath there were Spring libraries spring-beans, spring-core, etc. with version 2.0.5 (yes – spring-2.5.5.jar was also there). It took me some time and brain-effort to identify my enemy. I checked struts2-spring-plugin POM. Everything became clear – this plugin imported Spring JARs in different versions than those I needed.

The Solution

There are two solutions to this problem.

First way
First one is to define Spring dependencies as separate modules i.e. this way. Maven will not import old JARs because you explicitly define which version you want.
Disadvantage of this solution is that you will have to know which components are used in runtime by your system – it’s not always obvious at the beginning and you will have to understand many runtime exceptions before you get the correct dependency configuration. It’s also possible that you will not set dependency for all Spring modules imported by Struts2 and in your classpath you will have almost all JARs with version let’s say 2.5.5 but still some Spring modules will be imported with version 2.0.1.

The other one
The second solution is to define Spring dependency this way. With this solution you have to exclude problematic dependencies from struts2-spring-plugin:


org.apache.struts
struts2-spring-plugin
${strutsVersion}


org.springframework
spring-beans


org.springframework
spring-core


org.springframework
spring-context


org.springframework
spring-web



This way you tell Maven that you only want struts2-spring-plugin and you don’t need it’s dependencies to be resolved automatically.
I think I don’t have to describe disadvantages of this solution – anyway this is my preferred way of doing this.

Lesson learned?

I’m not going to throw away Spring and Struts2 integration because I have some problems with it. OK – I lost some significant amount of time on investigation the problem and finding the solution but I now know that I have to careful with Maven2 dependencies. Even if you depend on some concrete version of some framework Maven will automatically download and add missing dependencies. The problem is that it can make real mess in your classpath adding unnecessary JARs and it will be difficult to find the problem occurring in runtime.

The truth is that even with tools that manage software dependencies for you you may encounter some problems like the one I described. Managing dependencies is hard and I have no simple answer how to do it right at once. When you develop Java project you should use Maven-like tool but it still is not perfect (and probably will never be). The good idea in general is to version libraries you use (JARs, DLLs, whatever) and to put the used version in the file name (Maven does that automatically).

It’s not very helpful tip but you should be careful 🙂 and most of the time know what dependencies will be included with the next library you’re going to use. You have to check for possible conflicts and resolve them by removing unnecessary dependencies.

I know it doesn’t help much but that’s the way it is – dependency management is hard. Full stop.

7 thoughts on “Maven supports JAR Hell”

  1. Thanks for your comment and a tip. However Maven DependencyManagement will not save me from dependency problem I had 🙂 It can save others developing my projects later on. But I still have to know the dependencies in order to configure DependencyManagement, right?

    Przemek

    1. No the dependency management won’t save you to learn about the involved dependencies.
      But you can force Maven to get exactly the version you need for your projects, which can be very helpful when dealing with a lot of dependencies and frameworks.

      And yes, Maven can be very painful.

  2. It’s not Maven – it’s dependency management IMHO. I don’t know better tool than Maven and I know that Maven is sometimes pain in the ass 🙂
    I think many people blame Maven for being painful and in my opinion it’s unfair. Maven is just like the messenger (don’t kill the messenger) who tells you that dependency management is really hard – no matter which tool you use.

    PS. I blame Maven also very often for all the pain it causes – but if I go back in my memories to the times when I was building my projects using Ant I see the bright side of Maven 🙂

  3. Been there, hated it 🙁

    And when faced with such problems, I always come to miss the feature of doing wildcard matching for dependency exclusions. Like this:


    <exclusion>
    <groupId>org.springframework</groupId>
    <artifactId>spring-*</artifactId>
    </exclusion>

    But maven does not support it…

Leave a Reply

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