Skip to main content

The pain of migrating from Ant to Maven

Posted by zarar on December 13, 2006 at 8:18 AM PST

So you want to migrate to Maven because somebody told you it's the greatest build system around? They're probably right but what they don't tell you is that the road to Maven success is through hundreds of land mines, open JIRA's, mailing list lies and enough internal bleeding to make you wish you had stayed with good ol' Ant even though the build file had reached 4000 lines.

Don't take my word for it, just ask anybody and everybody who's ever converted a project larger than 30 source files from Ant to Maven. God forbid if you're dealing with XDoclet and generated files, if that is the case, then you'll need enough Valium to last you a mvn deploy and no you can't use -Dmaven.test.skip=true. Let's keep the attempts at humor on the side and talk about what really plagues Maven. You, you right there who’s reading this and thinking, Has this guy lost his mind? Maven is the greatest thing since the wheel and I've never run into any problems with it so it must be great. You my friend have obviously never worked with a multi-module project or mastered the maven-release-plugin which was originally designed to keep ADD patients occupied for months on end.


Let's look at the good first:

Maven does all the dirty work for you. Given a set of Java source code, it will take you no longer than two minutes to set up a Maven directory structure (using the archetype plugin), compile your sources and jar them up. Given a set of Java sources, web.xml, and other web resources, it will take you no longer than two minutes to create a fully functional war ready to be deployed. If you have any test classes, it will even run them for you without you even having to write a single line of XML.

As you can see the convention over configuration thingy does make sense. To accomplish the above tasks, you would be required to write a verbose Ant script but with Maven you have to write almost nothing. Note to Maven virgins: Maven by default assumes your source classes are in src/main/java, test classes in src/test/java, and web resources in src/main/webapp. It also assumes you have the ability to find a grain of salt on the beach when it comes to the mailing list but more on that later.

Continuing with the good, Maven's greatest feature (and it is a HUGE feature) is dependency management which it does a fine job of. No more downloading xfire-1.2.zip, unzipping it and adding it to your classpath. Almost everything is on ibiblio and you can download it from there using a reference in your pom. If something is not on ibiblio or if you’re dependent on a home grown artifact, Maven allows you to install the artifact in your local or remote repository with minimal bruises. Your application can literally be built from scratch by using a single call to mvn package.

Let's now look at the good and bad:

The first thing to do when working with Maven is to get the bleeding edge version (2.0.x) which has as many fixes as possible and then read Mergere Inc.'s book. Same goes for any plugins that you'd use. One of the better things about Maven is that it's very easy to develop plugins to fit your need; this of course might also explain why all the plugins have bugs in them.

maven-antrun-plugin

What this plugin is saying is this: We know Maven has problems and it can't do everything but we want to say that it can do everything so here's a blanket piece of code which covers all cases, granted with piss poor quality.

The maven-antrun-plugin for those of you who've never had to use it allows one to write Ant scripts inside a pom.xml, allbeit with strait-jacket type restrictions. If you plan on doing a medium sized Maven migration, be sure to familiarize with the evils of this plugin:

  1. It does not pass Maven properties to the Ant script unless you specifically redefine each property inside the Ant script, thus defeating the whole purpose.
  2. There is no way of passing Ant properties to Maven.
  3. It magically runs twice if a parent pom also has a maven-antrun-plugin defined, an entirely possible scenario.
  4. It does not allow for macrodefs to be called.
  5. It has class loading issues if you plan to use Ant optional tasks.
  6. It ideally should be used as a last ditch effort to perform tasks but has become a primary means of building with Maven.

So although the antrun plugin is powerful, it is a direct contradiction to Maven's philosophy of convention over writing-big-ass-scripts-to-do-build-tasks.

Plugins in General:

The power of Maven lies in its plugins. If there is a task that needs to be performed, say compilation, it's handled by the maven-compile-plugin; if you need to run tests, the maven-surefire-plugin; if you need to filter files, the maven-resources-plugin; you need to precompile JSP's, the jspc-maven-plugin. You get the idea.

The problem is that so many of the plugins hosted at Codehaus or Apache have bugs and "features" that will keep you up for nights wondering if you'll ever get to resume your FIFA game. Allow me to point out a couple brisk examples for the unbelieving audience:

  1. Plugins getting executed in no particular order: Not really a specific plugin issue but more of a general one. If you have multiple executions for a plugin bound to the same stage, they execute in, get this, RANDOM ORDER. Instead of caring for our sleep and executing them in the order they were defined, which would make too much sense, the Maven developers have decided to throw a curveball our way. This will make you wish there was another phase between process-classes and test-compile, guaranteed.
  2. maven-resources-plugin: Treats even built-in property values as verbatim. ${project.build.directory} will not be evaluated to "target" which it should be but to ${project.build.directory}. This can drive you insane if you're filtering for different environments.
  3. maven-surefire-plugin: Despite pleas by the general public to add a configuration parameter for having an additionalClasspathElements, the surefire plugin is committed to pissing people of by forcing them to have a custom version which they will need to patch and deploy to their local repository.
  4. maven-release-plugin: If anybody has got this working with a hierarchical project, please let me know.

The maven-idea-plugin, or sorry, the idea-maven-plugin was a pleasant surprise. Yes, the name matters. If you start your plugin name using "maven-", you better be part of the Apache group or they nuke your PC. Back to the plugin: Type in mvn idea:idea and you have a bunch of IntelliJ files which get you fairly close to compilation as long as you're doing nothing fancy. Unfortunately, if you're doing anything remotely complicated like generating source files or resources, you'll have to fiddle with the settings (classpath, source tree, XML files etc) to get it to do anything. All in all, it was a decent find.

When well written like the jspc-maven-plugin, life is good. If written like the Cargo Maven plugin, it lulls you into a false sense of security, shoots you and then gnaws your head off while you’re sleeping on your keyboard. Chances are that if you're going to decide to use a plugin beyond the most basic case, you will end up looking in its source code to figure out what the hell its doing. That's probably why every plugin has a convenient link to the source repository. What better documentation!

Generated Files:

If you're using XDoclet, you fall into two categories of people: Those that have fixed a bug in XDoclet and are using a custom version and those who haven't contracted rabies from a careless XDoclet developer error. If you fall into the latter group, you can go ahead and use the maven-xdoclet-plugin and see how far it gets you. However, if you fall into the former category, what lies ahead is the unenviable task of copying all the stuff you did in your build file over to your Maven POM and in to the antrun plugin. The other thing you can do is to force the xdoclet-plugin to use your custom version of XDoclet rather than getting it via its defined dependencies. Good luck going down that road.

Maven completely missed the boat on generated sources. Since all the source code is supposed to be in src/main/java, one is forced to do one of two things, both not very glamorous:

  1. You could dump them in src/main/java in the generate-sources phase and delete them in some subsequent phase after compile. Stinks of bad programming and will probably get you fired.
  2. Create a gen directory in target or as Maven likes to call it ${project.build.directory} and then use the build-helper-maven-plugin's add-source goal which will add another 25 lines to your pom.xml, making you nostalgic about Ant. Side note: is it just me or is ${project.build.directory} excessively long an alias for the word "target". Same for target/classes which the Maven folks happily defined as ${project.build.outputDirectory}. How nice and compact.

I hope you're a religious man if you're generating a web.xml or a struts-config.xml because the only way you'll ever get them in the WEB-INF folder is through prayer. Although the following misuse of the maven-resources-plugin would work too:

<resources>
   <resource>
      <directory>src/main/resources</directory>
   </resource>
   <resource>
      <directory>target/gen/web-resources/WEB-INF/</directory>
      <filtering>true</filtering>
      <targetPath>../wardir-cant-use-property-here-due-to-bug/WEB-INF</targetPath>
   </resource>
<resources>

See, if you want to copy over a resource other than the default one, you have to redefine the default resource (the first resource element). The second resource element's targetPath assumes the default location of target/classes which makes the value of targetPath rather confusing to the untrained eye.

Maven should seriously consider having a target/gen/src/main/java etc structure to combat generated files since this is a situation faced by most medium to large projects.

Again, there is a solution but it’s not pretty.

Profiles:

Remember the good old day of AntContrib's if/else statement? How simple? If or else. Two options, pick one of them. Well in the ultimate example of killing a bird with a cannon, in order for one to execute an if/else statement in Maven, you must write a profile which stores the plugins that execute on the true condition. If you want something else to happen in the false condition, write another profile. Sounds overkill? It is. Well, you could always use the maven-antrun-plugin but that won't get you far since you can't call Maven code from Ant code and you'll be forced to do everything with Ant once you go down that road.

Having said that, profiles do allow executions to run in different conditions. For example, if you only want your JSP's only to be precompiled when you're deploying to production, you would define the jspc-maven-plugin in a profile with an id of "precompile-jsps" and it'll only get executed if you activate it, for .e.g.: mvn -P precompile-jsps deploy. But say if you want to copy a file into either dir1 or dir2 based on ${prop1}, whip out the antrun plugin.

Class Loading

Justifiably Maven's class loading mechanism is complex, especially when it comes to multi-module hierarchical projects. They do have the decency to provide you with different classpath's which are built from the dependencies specified in the POM. Although this is very convenient, you just never know when you need to specify a weird property out of the blue.

Mailing List

Exhibit A, The Maven Users Forum. Where the users providing help are as confused as the poor bastards asking for it. For every query typed into the search box, six different "solutions" pop back all claiming to work but rarely ever doing so because you have something sliiiighhtly different somewhere. These posts usually have a link to a JIRA of some sort which may or may not be related to the actual problem. What I'm trying to ramble through is that the mailing list has tons of useful information in there but you have to be careful for what you accept as a solution as it's pretty easy to get lost.

Let's take a look at the bad:

Properties:

This bothered me to no end and I ended up writing properties-maven-plugin which still hasn’t been accepted despite many users supporting the feature. The feature in question is loading various project properties from property files. It only make sense but this essential piece of the puzzle in any application is mysteriously missing from Maven. For example, I want to load a different property file for production versus for development. Is that too much to ask? What Maven wants you to do is to define two profiles with their own set of properties and activate one of those profiles during the build (and hope that the profile properties override the project properties which they don’t). This makes the POM unnecessarily huge since defining properties in XML is a bitch:

What would be this:

myprop1=myvar1
myprop2=myvar2

becomes this:

<properties>
<myprop1>myvar1</myprop1>
<myprop2>myvar2</myprop2>
</properties>

Note you have to do this each time for every environment in your POM. Those of us coming from Ant can’t get a handle on this. There needs to be a replacement for in Maven. Don’t even think of using the maven-antrun-plugin to do that.

Oh, by the way, the ${line.separator} property is broken in Maven and if you try printing it out, you'll only be disappointed.

Multi-Module compilation:

Be very careful in a multi module project. If you’re doing a mvn compile, it will look at your sources in all POM’s that have a version number ending with the word SNAPSHOT, however, if you’re doing a mvn package, Maven decides to ignore your checked out sources but instead looks in the local repository for a snapshot jar. To avoid any possibility of compiling against older source, it’s best to do a mvn install for the module that’s serving as a dependency before doing a mvn package for the main module. Remember this and you’ll never find yourself doing an mvn package for hours without figuring out why your changes aren’t showing up.

Maven’s whole deal of treating –SNAPSHOT versions differently is bound to cost anybody a few hours of headache but once you’re past the initial growing pains of somebody shoving a philosophy down your throat, you’re bound to like it.

So in closing: Maven has been a complete pain to work with but it has pleasantly surprised me at how integrated my build has become and how source code from multiple modules is combined together to produce multiple artifacts. Something Ant just couldn’t do.

Related Topics >>