Why you should use the Maven Ant Tasks instead of Maven or Ivy

Like many others I really like Ant and I intend to keep using it until something better comes along. And I strongly feel that when you try to use *only* Maven for everything, you give up control over a few things.

Are there things that Ant does better than Maven? Oh yes, IMHO. Here are a few examples of typical Java web-app build related stuff that turn out to be “edge cases” in Maven meaning that you have to struggle, read up on obscure plugins or resort to ugly hacks in order to get things to work.

Replacing text in files only for development mode

<target name="jetty-setup-dev" unless="production.mode">
	<echo>making webapp changes for development mode...</echo>       
	<replace file="target/${war.name}/WEB-INF/web.xml" 
		token="org.apache.wicket.protocol.http.WicketFilter" 
		value="info.jtrac.wicket.devmode.ReloadingWicketFilter"/>                  
</target>

The example above shows how I switch to a custom Wicket filter in web.xml that supports hot-class-reloading and debug logging – but only in development mode. The Ant replaceregexp task can give you even more regex driven find-and-replace goodness.

Capturing input from the user

<target name="confirm">
	<input message="Are you sure?" validargs="y,n" addproperty="input"/>
	<condition property="abort">
		<equals arg1="n" arg2="${input}"/>
	</condition>
	<fail if="abort">User aborted.</fail>       
</target>

All the best trying to do that in Maven. Oh and using the maven-antrun-plugin doesn’t count – that’s cheating ;)

ZIP a bunch of files from where *you* want into what *you* want

<target name="dist-jtrac-src">
	<mkdir dir="target"/>
	<zip destfile="target/jtrac-src.zip">
		<zipfileset dir="nbproject" prefix="jtrac/nbproject"/>
		<zipfileset dir="etc" prefix="jtrac/etc"/>
		<zipfileset dir="src" prefix="jtrac/src"/>
		<zipfileset dir="." includes="*.*" prefix="jtrac"/>
	</zip>         
</target>    

Maven apologists will promptly point you to the great Maven Assembly Plugin. Sorry, no thanks. I sincerely tried to understand how to use *that* piece of work a few times in the past but gave up in despair.

Execute an arbitrary Java class with control over everything (args, classpath, etc.)

<target name="hbm-export" depends="compile">
	<input message="Hibernate Dialect:" addproperty="dialect"/>
	<java classname="org.hibernate.tool.hbm2ddl.SchemaExport" fork="true">
		<classpath>
			<path refid="test.classpath"/>
			<path path="target/classes"/>           
		</classpath>            
		<jvmarg value="-Dhibernate.dialect=${dialect}"/>
		<arg value="--text"/>
		<arg value="--delimiter=;"/>
		<arg value="--output=target/schema.sql"/>
		<arg value="src/main/resources/*.hbm.xml"/>
	</java>
</target>

This is a target I keep around to forward-engineer the DDL from my Hibernate entities on demand, for any database of the user’s choosing – just enter the dialect at the prompt.

There are times when even Ant may not have all the answers and a technique I use (or misuse) effectively is to use a custom Java class to extend Ant. So I don’t even need to learn how to create custom Ant tasks and mess around with the Ant classpath ensuring that the build remains “xcopy” friendly. Now, the “Maven way” to handle custom stuff is to write a custom plugin. Which is the easiest thing in the world right? Heh – don’t get me started.

There are quite a few more examples I can think of but that’s not the main point of this blog post. Hopefully I have made a case for “why Ant” above. A deal-clincher for me is the fact that all popular IDE-s have excellent support for Ant. NetBeans (which I use) takes it to a whole different level of tight integration – for e.g. you can map Ant targets to toolbars, shortcuts and things like that. Yes, IDE support for Maven is getting there but not as good as Ant IMO.

But am I saying that you shouldn’t use Maven at all? Not really. Let me explain.

For Java projects, the essential one thing that Maven does well and Ant cannot – is dependency management. So IMO the way to go is to use *both* Maven and Ant. The problem is that Maven obviously goes out of the way to *not* use Ant.

Fortunately, there *is* a way to get the information about dependencies from your Maven “pom.xml” project file across to your Ant “build.xml” file. I am referring to the Maven Ant Tasks. The thing is – there is very little documentation out there on how to use this. My conspiracy theory is that the Maven guys don’t want you to know about the existence of the Maven Ant Tasks. This is backed up by the fact that the two options for using Ant mentioned here in the official Maven FAQ cunningly avoid linking to the actual Maven Ant Tasks! (update: more than half a year after this blog post was published, the Maven FAQ was updated with a new entry “How can I use Maven features in an Ant build?” :)

I’ve explored custom plugin based approaches for bridging Maven and Ant with some success, and I’ve gotten some good feedback and even patches for the NetBeans specific integration that I tried for. I wasn’t aware of the Maven Ant Tasks until recently because as I said, the Maven guys have done a good job of making it hard to find. So I hope this post helps improve things on this front.

How to use the Maven Ant Tasks
I’ll use my Seam / JSF vs Wicket comparison project as a reference. First create a standard pom.xml like you would normally as in this example, just focus on dependencies.

This is the magic snippet that goes into build.xml that establishes the link between Maven and Ant:

<target name="mvn-init" unless="compile.classpath" xmlns:artifact="urn:maven-artifact-ant">    
    <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" 
        classpath="lib/maven-ant-tasks.jar"/> 
    <condition property="maven.repo.local" value="${maven.repo.local}" else="${user.home}/.m2/repository">
        <isset property="maven.repo.local"/>
    </condition>           
    <echo>maven.repo.local=${maven.repo.local}</echo>
    <artifact:localRepository id="local.repository" path="${maven.repo.local}"/> 
    <artifact:pom file="pom.xml" id="maven.project"/>
    <artifact:dependencies pathId="compile.classpath" filesetId="compile.fileset" useScope="compile">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
    <artifact:dependencies pathId="test.classpath" filesetId="test.fileset" useScope="test">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
    <artifact:dependencies pathId="runtime.classpath" filesetId="runtime.fileset" useScope="runtime">
        <pom refid="maven.project"/>
        <localRepository refid="local.repository"/>
    </artifact:dependencies>
</target>
  • You need just the maven-ant-tasks JAR in “lib” as per line #03, if you are picky about that, you can even consider downloading it automatically as part of the build. You can find details on how to do that in this blog post on how to use the Maven Ant Tasks instead of Ivy by Josh Suereth (who deserves credit for some of the ideas in this post).
  • I prefer bootstrapping the Maven integration within a target instead of globally for the build so that targets that don’t depend on Maven can run faster. Also, I don’t have to “pollute” the entire build.xml with the XML namespace stuff, it is isolated to just within this one target.
  • The unless=”compile.classpath” in line #01 is a nice way to ensure that the bootstrapping happens only once even when Ant executes multiple targets that depend on each other.
  • Lines #04 – 06 allows the user to override the default local repository location by having a “maven.repo.local” property entry set for e.g. in “build.properties”. You can even use relative paths here – so all your dreams of having Maven builds that work even when you copy the project folders across to some other machine – can come true ;)
  • The rest intializes classpath and fileset variables for compile, test and runtime scopes – which should be sufficient for most WAR projects.
  • Something that really surprised the heck out of me is that you don’t need Maven installed for all this to work! You are getting all the benefits from a single teeny tiny JAR file. Spread the word, people!

Now you can write your compile target like this, note the ‘classpathref” on line #03:

<target name="compile" depends="mvn-init">
    <mkdir dir="target/classes"/>        
    <javac srcdir="src/main/java" destdir="target/classes" classpathref="compile.classpath"/>
    <copy todir="target/classes">
        <fileset dir="src/main/java" excludes="**/*.java"/>
    </copy>         
    <copy todir="target/classes" failonerror="false">
        <fileset dir="src/main/resources"/>
    </copy>              
</target>

I normally use Jetty for development (like this) so I don’t need to assemble a WAR most of the time. People who haven’t tried this really don’t know what they are missing, huge time saver – not to mention tight IDE integration for things like debugging. But when the time comes to build a WAR this is how you could do it (note the reference to the Maven fileset at line #11):

<macrodef name="war-exploded">
    <attribute name="deployDir" default="target/${war.name}"/>
    <sequential>
        <copy todir="@{deployDir}">
            <fileset dir="src/main/webapp"/>
        </copy>
        <copy todir="@{deployDir}/WEB-INF/classes">
            <fileset dir="target/classes"/>
        </copy>
        <copy todir="@{deployDir}/WEB-INF/lib" flatten="true">
            <fileset refid="runtime.fileset"/>         
        </copy>
    </sequential>
</macrodef>          

<target name="war-exploded" depends="compile">
    <war-exploded/>        
</target>

<target name="war" depends="war-exploded">       
    <zip destfile="target/${war.name}.war" basedir="target/${war.name}"/>
</target>

Note that I don’t even use the Ant “war” task, I just ZIP ;) Maven evangelists would have you believe that Ant leads to un-maintainable, verbose build scripts that are hard to re-use across projects. I call bullsh*t. Look at the Ant “macrodef” usage above which means that I can reuse the WAR routine and do something like this for Tomcat later:

<target name="tomcat-war-exploded" depends="war-exploded">
    <war-exploded deployDir="${tomcat.home}/webapps/${war.name}"/>
</target>

What about Ivy?
Whoa, I never thought that this would turn out to be such a long post, let me end by addressing the question: should projects use Ivy instead?

My opinion is NO. Actually, the trigger for this blog post is that I recently joined a project-team already using Ivy and I’m having a hard time adjusting. Anyway, trying to be objective, here are my reasons:

  • One of the good things about Maven is that it enforces the closest thing we have in the Java world to a standard directory layout structure. I find it hilarious that Ivy falls into the same trap as Maven in that they can’t say good things about Maven just like the Maven guys avoid saying good things about Ant. So you won’t find any recommendations to use a standard project structure in the Ivy “best practices” documentation ;) Ivy projects tend to use ad-hoc directory structures, this is what I have observed.
  • Maven obviously has been around longer than Ivy, this translates into more documentation, more users who are used to working (and struggling ;) with it etc.
  • IDE project descriptor creation support: For example, Ivy has no official way to create your Eclipse project descriptor files for you. This itself would be a reason for me to avoid Ivy. There are some ways to manage your Eclipse classpath like IvyDE which honestly doesn’t have much documentation and something like this and this Google code project which don’t look too stable. If you have a Maven pom.xml and Maven installed, just do “mvn eclipse:eclipse” and you can even add “-DdownloadSources=true” – and you can ensure that even things like the “output folders” like “target/classes” and “target/test-classes” – are standard across your team.
  • Needless to say, using the Maven Ant Tasks approach gives you a smooth migration path to Maven. If you do it right, nothing prevents Maven die-hards on your project team from using only the pom.xml and avoiding Ant completely. This is how I have set up the JTrac build for the last 3 years.
  • Maven is positioned as a “project management and comprehension” tool (whatever that means ;) and there is certainly a good ecosystem of plugins that for e.g. can generate your project web-site, run Checkstyle reports, etc.
  • As someone already using Maven for a while, I found it really unsettling that having to use Ivy for another project was resulting in a ton of JAR duplication on my hard disk. And why on earth did the Ivy guys decide to re-invent the structure in which JAR files are stored in the local repo? So you can’t reuse your Maven repo or copy stuff around.
  • Something that the Ivy guys would hate to admit is that ultimately you end up downloading JARs from the Maven public repositories only. So all that talk of doing a better job than Maven kind of sounds flat. People are quick to blame Maven when resolving of dependencies fails to work as expected but this is usually the fault of whoever setup the metadata in the repositories. There’s not much that Ivy can do to solve that problem.
  • I think Maven multi-project support where you keep common stuff in a parent POM and have projects depend on each other is better than how you would do it in Ivy (although I haven’t explored this fully yet).
  • The only thing that appears to be compelling about Ivy is that doing “exclusions” is far less verbose than how you would have it in Maven. But if the Maven guys just fix this one issue

As an example of the kind of thinking that Ivy encourages which I don’t agree with is this quote from a blog post by Dan Allen (Seam committer and author of “Seam In Action”):

But the most absolutely vital feature of Ivy, and what makes it infinitely more useful than Maven 2, is the fact that you can disable transitive dependencies.

I’m outspoken about my position on transitive dependencies. I see them as both evil and a silly device designed for novices (and people with way too much time on their hands). It makes your build non-reproducible and unstable and in the end causes you more work than the work you were attempting to eliminate by moving to Maven 2. This “feature” really is Maven’s boat anchor. Trust me on this one, it’s trivial to define which libraries your application depends on. There really aren’t that many! And you can put all the worrying aside about exclusions.

All I’ll say is that if you disable transitive dependencies, then what is the point. You might as well write a script that downloads a bunch of JAR files listed in a properties file. And about the statement that “there really aren’t that many!” – I really don’t agree. Take Seam itself as an example of a framework that has so many dependencies (and sub dependencies) and expecting developers to keep track of the sub-dependencies (instead of the project-owners who should be updating the Maven repository metadata correctly) – just seems wrong to me.

Did I miss any reasons for not using Ivy? Do let me know in the comments!

About Peter Thomas
https://twitter.com/ptrthomas

23 Responses to Why you should use the Maven Ant Tasks instead of Maven or Ivy

  1. In all my presentations I call out Ant as a good tool. I usually draw the distinction of a tool versus a framework. Where Maven is tool with processes and patterns associated with its use which makes it more of a framework.

    We also made it possible to write Maven 2.x plugins with Ant and we (Sonatype) have a number of clients who use Ant-based plugins. To the user there’s no difference in use.

  2. Jim White says:

    I’m an Ivy guy that has no problems saying nice things about Maven. My comment though is that you missed the enormous reason *to* use Ivy. Which is that it is perfect for adding automatic dependency management to *existing* Ant projects. Which highlights the trouble with Maven for existing projects, which is that it is useless since it expects you to throw out a working system in order to use it. And while I’m pointing out Maven problems, it is only really practical if you manage your own repository. If you want to publish OSS projects that rely on public repositories, Ivy has the essential ability to work around bad metadata.

    I’m glad to see Maven finally come up with Ant tasks (years after Ivy got started), and I’m sure that we’ll eventually end up with a good method for transitioning legacy Ant projects into Maven. And Maven very likely will extend itself in that direction, but it is definitely late to come around to recognizing that “start over” is totally a non-starter for many projects whose only real pain is manual dependency management.

  3. Jim, the Maven Ant Tasks have been there for years. The maven-artifact code was usable outside Maven from day one and the Ant tasks were available pretty soon after the release of 2.x. Maybe we didn’t advertise them very well but they were there.

  4. SS says:

    I guess the right answer, as with any tool, is “it depends” (on the nature of the project in question).

    From an SI’s pov, where a majority of our projects are standard web-apps (with EJB/Spring) – the feature I’ve come to appreciate is the lifecycle concept (along with the standardized folder structure you already mention – they go hand-in-hand). Think of it as a pre-defined ant file where the requried targets and their ‘depend’s are already declared – saving you the need to copy-paste boilerplate ant-code every time you start a new project!

    And for when you need to step out, you have the antrun-plugin. I know you called that out – but I guess it’s as much cheating as doing it the other way around – Maven from Ant ;) Bottom-line – it gets the job done ….

    One other point on IDE support – instead of eclipse:eclipse – try using m2eclipse (Sonatype product – @Jason (and Eugene) – Thank You!). It essentially adds a dynamic classpath to you project that indirectly references jars from your local repository as required by your pom.xml. No generation – no need to keep things in sync etc. And it even supports multi-project poms ! [Side-Q: How easy/difficult are those in the Maven-from-Ant approach?]

    Finally – as for Ivy – I think I read somewhere that one of the biggest advantages is the concept of “configurations” – which allows you to define different sets of dependencies. It does mean more metadata for projects – but the flip-side is that it solves the problem of having to do tons of manual excludes – perhaps the biggest PITA wrt Maven.

  5. SL says:

    IMO, “Why you should use the Maven Ant Tasks instead of Maven or Ivy” – it’s not convincing, not convincing at all.
    Both tools have almost equal feature list (and I’ve been using both for years). The only diffience is that Maven projects more maintainable – less script to write, and less to maintain. When you have dozens of projects to build and maintain – you can see clear advantage of Maven.

    1) “Replacing text in files only for development mode” – Maven uses more comprehensive coverage of such needs – profiles – see http://maven.apache.org/guides/introduction/introduction-to-profiles.html

    2) “Capturing input from the user” – IMO it is not good thing for automated and reproducible build.

    3) “ZIP arbitrary files …”
    I’ve started using Assemby plugin straight out of box with no problems. BTW there are few examples avaialble at http://maven.apache.org/plugins/maven-assembly-plugin/examples/index.html
    Dependency plugin is also helpful

    4) “Execute an arbitrary Java class with control over everything…” you can use Exec plugin to do the same – http://mojo.codehaus.org/exec-maven-plugin/

    5) “Maven evangelists would have you believe that Ant leads to un-maintainable, verbose build scripts that are hard to re-use across projects. I call bullsh*t”

    Heh, I guess the you’re ANT evangelist. Many people who complained about Maven are actually haven’t tried to understand Maven’s concepts and go from there. But they tried to use Maven from different prospective (e.g. ANT) and that obviously failed.

  6. Josh Suereth says:

    Sorry, dude, the comparison should no longer be Maven vs. Ant. I think Maven has won the “Full-Project lifecycle” battle (which ant never tried to win). However ant is still competing with better ‘scripting’ builds (like rake + gradle). Ant architecture is nice for scripting, but XML is *not*. I agree with your estimate of maven-ant-task vs. ivy (even blogged about it myself), however I disagree with throwing out maven. I don’t really want control of all the “mundane” aspects of my build (like where to place artifacts), as long as I can find them when they’re done and do interesting things (like multi-module projects).

  7. Peter Thomas says:

    Some comments on the above comments:

    @Jim White at #2: Agree that Ivy can make sense for “legacy” projects. But it can be argued that migrating a legacy project to a “Maven structure” is not that difficult. Actually if you think about it, for typical web-app projects, no matter the folder structure, the Ant script only needs 3 things from Maven:

    – compile.classpath
    – test.classpath
    – runtime.fileset

    Not really a big deal and easy to migrate, I’ve done it a couple of times.

    @SS at #4: I think these are very good arguments. A few points:

    – I am personally not that thrilled with the “lifecycle” concept, it adds to the steep learning curve of Maven, it locks you into what Maven provides, and I would argue that it is nothing but a “glorified depends“.
    – “saving you the need to copy-paste boilerplate ant-code every time you start a new project” – oh come on – say, how often do you start a new project? Be honest :)
    – Think of it as a kind of “separation of concerns” – I prefer keeping dependencies in the Maven POM and the Ant stuff in a separate file, but that’s just me. I also like being able to double-click on targets in the Eclipse “Ant View” for instance.
    – Good question, I have never tried multi-project with the Maven Ant Tasks. I’ll add a comment if I get around to it soon, maybe someone else can comment.

    @SL at #5:
    You made your points well, I leave it to readers to look at the links you posted and decide for themselves.

    @Josh Suereth at #6:
    You know, making bold statements like “dude, technology X has won” is not considered good form especially when you don’t provide any evidence ;) Anyway, I have looked at Rake and Gradle and I’m not convinced yet for 2 reasons:

    – non-trivial to set up and not ubiquitous
    – can’t beat IDE support for Ant

    But this can certainly change in the future. I clearly began this post by saying that I intend to use Ant “until something better comes along”. Cheers!

  8. Josh Suereth says:

    Ah, my point was more that ant a maven solve build issue, but take very differing approaches. Comparing them is like trying to compare Ruby to Haskell, or Python with Scala. They’ve gone differing routes in trying to solve a problem, and therefore appeal to different audiences. I tend to lean on one side of the fence in my preferences, but am I willing to use both in tandem.

    It sounds like we agree, but lean towards opposite sides of the fence. It’s been my experience that the ease of creating/moving/adding projects is highly useful. It’s not amazingly frequent, but perhaps once every other month I’m faced with the dilema of creating a new shared/plugin “module” for my project, or just hacking something in where it doesn’t really belong so my classpath is sane. I’m guessing the ant + maven-tasks makes this a hell-of-lot easier than when I was using ant. So, as long as you can “stay agile” then kudos to you!

  9. JW says:

    I was happy to find this discussion as it validates what I have done. Our builds are a little non-standard so I always had trouble getting them to fit the Maven lifecycle.

    My own setup mirrors this pretty much exactly. I use maven tasks for handling dependencies, generating IDE config, and deploying to repositories, and use the ant framework for everything else.

    If I were starting a fresh project, I probably would pick Maven, but for where I am today, this is absolutely perfect.

  10. Kamal Govindraj says:

    I have been using Maven for over 3 years now. I would prefer to use Maven and fall back to the ant-run plugin or the groovy plugin in case you need to do something special.

    Overall I think the tools that try to enforce certain standards and get a critical mass of people to use it do a whole lot of good. The maven plugins (jetty, pmd, checkstyle … ) are so much more easier to integrate into an existing project than are simillar ant tasks.

  11. mycrappytechnologyblog says:

    Nice post, I am looking to use Maven in some legacy Apps. I may try to do one with Maven an done with Ivy to see which is easier.

    On a separate note, I have a blog that is hosted on WordPress, and I have not been able to figure out how to include the “code” layout like you have. How did you do that?

    Thanks

  12. Peter Thomas says:

    Refer the “Posting Source Code” part of this article:

    Code

  13. sj2004 says:

    You can use maven assembly only for simple stuff. Anything complicated you have to reply on ant task or write your own mojo.

  14. Carter Sanders says:

    Thanks for the great examples. Are these code snippets copyrighted?

  15. Peter Thomas says:

    @Carter Sanders: Snippets are in the public domain, not copyrighted, feel free to use.

  16. magomarcelo says:

    unfortunately there are serious problems when sharing properties between pom.xml and ant, expansion does not work (try ${project.build.finalName} and no way to reference contents of pom by xpath or something more complex than direct references or indexed properties which is pretty useless unless you want to rely on the order of what you put in the pom

  17. Dennis Lundberg says:

    @magomarcelo: You cannot use ${project.*} in an Ant script to access Maven properties, because ${project} refers to the Ant project itself. Using code samples above you should instead use ${maven.project.build.finalName} where “maven.project” is the refid used in the task.

  18. Alex Worden says:

    Another reason not to use Ivy… have you read the documentation? OMG! It’s far worse than the Maven documentation – and that’s saying something!

  19. Pingback: Db4o via Maven « Find Time for the Karussell

  20. Vamsi says:

    I wonder why? using ivy is a best practice as per ANT
    http://ant.apache.org/ivy/history/trunk/bestpractices.html

  21. Peter Thomas says:

    @Vamsi – technically, Ivy became a sub-project of Ant when it moved / graduated as an Apache project:

    http://www.mail-archive.com/general@incubator.apache.org/msg15445.html

  22. About the blog post by Dan Allen you’re citing: You are so right! Almost every grown enterprise Java product will end up with hundreds of dependencies after some years of development. Not using transitive dependencies here will produce all kind of epic errors at runtime while during build and test everything seemed fine.
    But if you happen to hit that tiny block without test coverage that uses that obscure library that unfortunately depends on the ancient Google collections jar… Well.. let me say that: With transitive dependencies, this code wouldn’t even have compiled.

  23. Pingback: Confluence: Technology

Leave a comment