Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Reasons to avoid Maven 2.0 for your next project

Java 2006

I've been a fan of Maven as an automated build tool for a long time. It combines the best of a number of different tools -- such as JUnit, Velocity, and Ant -- into a combined tool that allows projects with many dependencies to be built automatically. It also imposes a particular project structure and way of doing things; much like an IDE, once you get used to it, you can work on any project that involves it with little difficulty. As standards go, it's like autoconf and automake for the Java world.

Maven 1.0 was released in September 2003 and although it was buggy at first, the subsequent point releases up to Maven 1.0.2 in November 2004 fixed most (but not all) of the annoying bugs. There's still a few known ones outstanding; for example, when you try and bundle an EAR file, it checks to see if the file's canonical path is the same as the absolute path. This is great up until the point where you use symbolic links or relative directories; in which case, the canonical path and absolute path are no longer the same and the EAR bundler complains.

Since then, Maven 1.1 has been in development, and more recently, Maven 2.0. Amusingly, instead of following an agile path (release early, release often) it's been more a case of developing a prototype and throwing it away, then developing a new prototype. Maven 1.1 was slanted to be upwardly compatible with Maven 1.0 on the whole; but the last sighting of Maven 1.1 was a beta release in September 2005 and no sign of any progress from Maven since then. It's my guess that Maven 1.1 will never see the light of day as a final, stable, or supported release, and that it will quietly be dropped in favour of Maven 2.0.

Maven 2.0, first released in October 2005 aims to address some of the deficiencies in Maven 1.0. However, it breaks more than it addresses, and so it's not something you should consider if you've got a suite of Maven 1.0 projects at present (and possibly, even if you are starting a new project). Development of Maven 2.0 is still ongoing; and it's likely that it will continue to be developed and supported if the developers haven't managed to alienate enough people so far. If they have, it will just grind to a halt like Groovy.

Everlasting betas work well for some people. Google Mail and Google News are examples of worthwhile services that have had the beta moniker for some time. At least with these betas, it was still possible to use the core functionality to achieve a particular job, and it's better to get early feedback so that you can fix issues later on. But you'll never fix all issues even in the beta cycle, and at the end of the day you have to release a system that's capable of running, whether there are known bugs or unknown bugs in the system.

Tools that manage projects and source code are examples of places where you don't want to be flying with a beta in production projects. The risks -- whether it loses your source code, or whether it prevents builds from happening for a couple of days -- are too great on anything but the smallest single-developer projects to risk running into unexpected problems. So in business, services with 'beta' are routinely ignored.

I don't think Maven 1.1 should be beta any more. It works, and it's got updated versions of the core technology (Jelly 1.0, Ant 1.6.5 etc.) that could give real benefits to projects that are running now. It's not as if Maven 1.1 is going to ever be managed or supported anyway, now that 2.0 is on the scene, so why not get rid of the beta moniker, say it's what you've got, and move on. Instead, it's left to rot in some bit bucket hell where it likely will never see the light of day.

The good, the bad, and the ugly

The good

The issue of transitive dependencies has been solved. Whereas with Maven 1.0, you had to define a dependency on both your immediate dependants and their descendants, in Maven 2.0, you only need to declare the immediate dependencies, and Maven figures out the rest. Good stuff.

The bad

In Maven 1.0, you had the opportunity to create a file called maven.xml, which would allow you to hook into the build process and execute additional steps before (or after) an existing goal. This allowed a pretty flexible tool to be even more flexible. Want to stick a copyright notice at the top of all of your source files before Jar-ing the source? No problem. Simply put in a preGoal for the jar goal, and it gets triggered automatically. Want to create your own goal, for example eclipse:install? Just put a goal in, and define it as an ant:copy that puts the files in a known location specified by a property eclipse.home.

This ease of extension/flexibility is one of Maven's key benefits. Not all goals will be ultimately flexible; even if they are, it may not be possible to customise it to do what you want. Being able to define (on a project-by-project basis) these extensions is key to being able to have a build process that you run, as opposed to a build process that runs you.

Granted, Jelly hasn't lived up to its expectations. But then like Groovy and the AWT, they were good ideas that failed to live up to their promise due to poor implementation rather than poor ideas. Mostly it's because the framework for building Maven plugins didn't take care of this automatically; plugin developers had to 'opt-in' to work with Jelly expressions. The contexts for defining properties was part of the problem; for example:

<j:if test="${eclipse.type} == 'plugin'">

would fail to work, whilst

<j:set name="eclipse.type" value="${eclipse.type}"/>
<j:if test="${eclipse.type} == 'plugin'">

would work, simply by copying the value of a variable from one scope to another. That's not an indicator that the idea is bad; it's an indicator that you've got a bug in your code. Of course, the automated tests would have picked that up. What do you mean, there aren't any?

That's not to say that Maven isn't extensible; it's just not as extensible as earlier versions. You can still write your own plugins into Maven to do certain things (though it's not clear whether you have much control over the order in which steps take place), but it's a bit like saying that Ant is extensible just because you can write your own Ant tasks. Yes, there are certain situations where a new Ant task is necessary, but on the whole you can get by with just amendments to the build.xml file. Maven has in effect decreed that there is no build.xml file, and everything you do must be write new tasks.

The ugly

None of the really bad ideas have been fixed from the last iteration of maven. Everything is still highly Java-specific (compile=run the Java compiler; test=run JUnit), and worse still, the concept of 'compile' and 'test' are still merged into one step. Granted, running tests is an excellent way of making sure that a system works; but forcing tests to run as part of a compilation step is both a waste of time, and an excellent exemplar of the fact that it's either Maven's way or the highway.

It wouldn't be so much of a problem if there were a separate step, such as compile, test and then a combined step of (say) 'build and test'. But when you're building large projects with Maven, particularly with lots of dependencies, it's desirable to ensure that the whole code-base compiles successfully before moving forwards with the testing. Obviously, if the error is at one of the key shared projects (say, a common utility project that's shared by everything) then it's likely that a break here will break the rest of the build. But if there's a compile error at one of the outlier projects, the build is going to be broken anyway. You don't want to have to waste time checking whether the functionality of all the dependent projects is the same as last time; you want to fail fast on the fact that the compile tree is broken so that you can get to the next build (and hopefully the fix).

It's unfortunate that Maven is perfectly capable of doing this. You just need to have a goal that runs the compile step (without turning on the test) and then runs the test step afterwards. You can then have a build step which attains both in series; but the advantage of the separate step is that when running in multi-project mode, if you attain the 'build' goal, it will attain the 'compile' goal on all sub-projects first, and then attain the 'test' goal on all sub-projects second, which is far more sensible. However, this suggestion gets shot down when posting a Maven bug report, based on the non-agile WAGNI principle (We Aren't Gonna Need It).

The practical upshot is that Maven has a number of good points and a number of bad points. The good points are getting better, and the bad points are getting worse. It's not clear that this situation is ever going to change.

On the other hand, Eclipse is gaining momentum, perhaps more so than Maven ever will. Although that too is heavily based on Java, the fact that every dependency is now based on OSGi bundles, means that it may be possible to have a Maven-esque build process that works at an OSGi level instead of the Maven Jar level. Perhaps that will be the time that Maven can be retired into the obstinate hole into which it has dug itself.