Archive for October, 2007

Maven – Curse or Blessing?

Today I spent several hours fighting a beast called Maven. I started using Maven some days ago for a new project. But each and every day I was asking myself the question: “Maven – curse or blessing?”. If you google around a litte bit, you can find lots of developers swearing in their blogs – and so was I today.

Everything was working nicely until I carelessly added several dependencies this morning. Suddenly, my Wicket-based webapp stopped working with some strange exception. A class in Jetty called a method that was not found in the servlet API (javax.servlet). Well, rather strange, I thought. After a little online-research I found that this method was introduced in version 2.5 of the servlet API. I doublechecked with the Jetty docs and yes, Jetty 6.1 implements exactly this version of the servlet API. Well … so what’s wrong?

But then I noticed that Maven added two different versions of the servlet API to my dependencies: 2.3 and 2.5. But isn’t Maven meant for resolving such conflicts? In deed it is and it actually wasn’t Maven’s fault. While Jetty comes with its own version of javax.servlet (called org.mortbay.jetty:servlet-api-2.5 in Maven), Maven added javax.servlet:servlet-api (version 2.3) as a transient dependency.

So how to get rid of unwanted dependencies? In Maven it is possible to exclude transient dependencies. The problem is that you have to do that for every package that depends on this unwanted package. You have two choices to find out which package depends on it: you either look at the POM of every dependency and the POMs of their dependencies … or you look at the dependency tree that can be generated with Maven’s dependency plugin (mvn dependency:tree). Well, the tree-task didn’t work for me, for whatever reason. I remembered that the dependency tree was also part of the project website that can be generated with Maven’s site plugin. I did generate the project’s website (mvn site) and there was also a dependency tree … but javax.servlet:servlet-api wan’t in that tree … but in the list of transient dependencies. If I remember correctly that was were I started swearing. So it was time to manually inspect all the POMs. (For the sake of completeness: I think that the servlet API wasn’t in the dependency tree as it only contains three levels of dependencies.)

I started analysing the POMs one-by-one and found out several interesting things:

  • Some POMs added junit as dependency without specifying the test-scope (it would therefore be added to the generated WAR file)
  • Optional dependencies and alternatives are not defined consistently: sometimes all optional dependencies are normal dependencies, sometimes none of the alternatives is a dependency, … altogether it’s quite messy

I finally found the dependency that added version 2.3 of the servlet-api to my dependencies: commons-logging. Yes, exactly that commons-logging that is basically meant to write some lines into a file. Why does it depend on the servlet API? Because it has its own logger that is supported by commons-logging and the commons-logging POM has dependencies on all supported logging implementations:

So you have to explicitly exclude the dependencies you don’t need … or you will deploy your desktop application with the servlet API. And the best thing of all: you have to do that for each and every dependency that introduces commons-logging to your project dependencies.

I then decided not only to exclude all unnecessary logging-frameworks but commons-logging all together in favour of slf4j. Unfortunately, it’s not possible to globally exclude a package all together – as Maven developers want to “protect” you from doing that – excluding commons-logging can get quite complicated. Erik van Oosten came up with a nice workaround for this topic. He announced a faked Maven repository that offers an empty jar file with version 99.0-does-not-exist for all artefacts you ask it for. So if you explicitly depend on this version, no lower versions will be added to your dependencies. And if you mark this dependency as provided, it also won’t be included in your WAR files. Yes, it’s a hack, but it’s a nice one ;)

Besides a small problem with empty jar files that Erik is going to fix (see my comment in Erik’s blog) the whole thing works like a charm – no commons-logging, no version conflicts with the servlet API, everything is fine.

I think I still can’t answer the question whether Maven is a curse or a blessing. The only thing I can say is that Maven has its problems, but also great power. I’d say it’s very important that you really go into the topic before you use it. Maven is big, *really* big. It’s not like Ant that you can start using after reading a short tutorial. If you don’t know how Maven works it will certainly kick your ass. But if you know how to handle that beast, it really kicks ass ;)

However, I am still not happy with the official Maven repositories. They are completely messy and Maven is not really helpful in coping with this mess. I would really love to see something similar to Debian package management: with provides, depends, suggests, recommends, meta-packages, nice updates … and a repository that is under tight control. Maybe even the new Ant subproject Ivy is a step into the right direction (I would love to see it in action together with Maven). But for the moment the only thing you can do is to have a close look on each and every POM in your tree of dependencies. Yes, it is extensive work, but it’s the only way to reduce your dependencies to a minimum … And dear Maven developers: please don’t try to prevent us from doing evil things, as we might do them anyway (like using version 99.0-does-not exist). Just give as the freedom to do what we want to do.