Having just recovered from the “culture-shock” from converting from Ant to Maven, I’d like to pass on a few words about the experience.
At first glance, Maven seems quite complicated. Rather than writing your own targets, Maven has dozens of its own, already written for you. Then comes the realization that all of the stuff you normally write in Ant scripts is there already; for many projects, you don’t need to write a thing.
Where It’s At
What you do need to do is follow a specific pattern as to where you put what. Source should go in “src/java”, for example. Output jars go in “target”, and so forth. These directories can be overridden if you need to, but it’s not very difficult to simply adapt to the Maven structure, and it makes for a good standard accross Maven projects.
One special type of location for Maven are “repositories.” There are “local” repositories, where Maven maintains versioned copies of all jar files required by your projects, and remote (possibly public) repositories, where versions of jar files reside ready for use in projects.
The Maven project definition (Project Object Model file, or POM) tells Maven what jar file dependencies are required for the specified project, and it will search a list of remote repositories (usually including http://www.ibiblio.org/maven) for the necessary jar files, and download them to the local repository (once) when required. This information is normally stored in project.xml.
All for One
The other concept that makes a lot of sense, once you get used to it, is “one project, one artifact.” Simply put, this means that any one Maven project produces a single “artifact,” or result file. This is usually a war file (for Web applications) or a jar file, either executable or not. Back when I was using Ant, I’ve found myself creating a couple of different targets that built different parts of an application—say, one to create the dependency jar files, and another to produce the finished webapp.
The terminology for Maven is not much different from Ant. Instead of “targets,” Maven talks about “goals.” Each collection of source files designed to produce a certain resulting file (artifact) is a “project,” just like Ant. When you see the scripting in Maven (if you ever do), it’s a lot like Ant; in fact, virtually every Ant task can be used within Maven scripts, with minor, if any, changes.
Chances are good, however, that you’ll not need to write as much custom scripting for Maven as you did with Ant. You’ll want to browse the list of available plugins on the Maven site (http://maven.apache.org) to get familiar with what’s out there.
Plug It In
Maven is oriented around the concept of a plugin, which, in short, allows you to add custom goals to Maven. A large collection of existing plugins has every goal that most projects need, meaning you have exactly no work to do—other than a bit of learning, that is. For the occasion where you do need to do some custom work, you can create a maven.xml file that is specific to one project, just like a build.xml file for Ant. For larger projects, however, you’ll likely want to re-use your custom goals, and creating your own plugin is the answer for this. A plugin is a Maven project just like any other, although it may not have any Java code associated with it if all you need are custom goals without any code. Once a plugin is created, you can “deploy” it to a repository, and the rest of your team can type a single command to download and install it on their local machine.
Plugins, just like every other component Maven handles, are versioned, so you can release updates to your plugin in an organized and controller manner.
Versions, Versions Everywhere
Maven is organized around the concept of versions, for plugins, projects, and dependencies. If you establish a reasonable version numbering mechanism, after a while you’ll wonder how you ever got along without Maven.
For example, your primary project might start with a version 1.0, let’s say. It, in turn, depends on service-database-1.0.jar. You simply declare this dependency in the project.xml, and Maven takes care of making sure that service-database-1.0.jar is in the classpath when you build your project. If it’s not already in the local repository, it searches all known remote repositories to try to find it, and downloads it automatically.
Later, you find a bug in service-database-1.0, and service-database-1.0.1 comes into being. Again, when your project is ready, you simply edit project.xml, increment the version of your main project to (say) 1.1, and build. Now, both 1.0 of the project and 1.1 are able to be built—so in the (unlikely!) event you, ahem, introduced an unintentional feature (a bug) in 1.0.1 of service-database, rolling back is very easy as well. Another month passes and an API change is made to service-database-1.0.1, making it now service-database-2.0. Your existing projects, all of which rely on service-database-1.0.1, with its older API, still work just fine. As they are upgraded to the new API, then simply declare the new dependecy on 2.0. No upheavals, no whipping the rug out from under existing projects when something changes.
Take a SNAPSHOT
In a team development situation, however, sometimes things move a bit faster than the above scenario can easily handle. If bug fixes and enhancements are coming thick and fast, you may want a way to say “this project depends on the very latest available service-database, whatever that is.” This is a good spot to use a SNAPSHOT. When creating a jar file for our service-database, we have the option to build and deploy either a versioned jar (for example, 2.0), or a SNAPSHOT. Snapshot is a special version that indicates a current development copy. Unlike regular versions, Maven will check for a new SNAPSHOT version in a remote repository for every build. For “normal” versions, once Maven has downloaded 2.0 (for example), it never tries to get a “new” 2.0. Once 2.0, always 2.0—to get changes, you’d need to go to 2.0.1 (or some other version). With a SNAPSHOT version, though, Maven will automatically fetch the latest SNAPSHOT every time you build your project. For rapidly moving code, this can be a necessity, particularly in a team environment.
Maven supports unit, integration, and functional tests. For unit tests, the default is JUnit, making the popular test framework trivial to put to use. Tests are kept (again, by default) in a seperate source tree, and need not be included with your deployed code (or jar).
Maven’s default setup is to actually refuse to build the project artifact (jar or war) if all available unit tests are not running successfully. You can override this behavior easily, of course, but it’s a good safety for production code.
Enough has been said about the absolute requirement for good testing that I won’t repeat the sermon here, other than to say that Maven provides the ideal backbone and structure for a comprehensive test suite at every level.
Maven is an excellent build tool, with powerful features for large projects. If that’s all it did, it would be invaluable. However, in addition to its build features, Maven also has plugins that contain powerful documentation and testing features.
With a single command, you can generate a (customizable) Web site complete with Javadoc, documentation (created from XML format source documents), and a huge array of reports, including such gems as “coverage” reports (that tell you how much of your code is being tested by your unit tests), and JUnit and other test reports.
The amount of useful documentation that can be created from even a basic project is truly amazing, and the integration that Maven’s plugins provide into many different existing (mostly open source) reporting and testing tools puts it all at your fingertips. It takes a bit of tweaking of configuration properties to actually get Maven to generate all of the available reports, and you will find your next build downloads a few third-party projects and tools as required to produce the reports.
Again, like everything else in Maven, the array of reports, their format, and many other features are driven from a simple file of configuration properties.
The Bigger, The Better
For large projects, Maven’s power becomes even more obvious. If your project gets beyond a certain size, it only makes sense to break it into one or more dependencies—for example, a number of jars with certain services or packages in it. If nothing else, it makes build times more manageable. Although there is a mechanism is Maven to build multiple projects at once (the “reactor”), you don’t often need it in my experience, once a project reaches a certain level of maturity. It tends to be more often that individual dependencies change one or a few at a time, rather than all of them at once.
With Maven, this is simple to do, and keeping track of what depends on what, and with what versions, becomes quite manageable. The project.xml file becomes the primary definition of the state of a project, and creating intermediate (snapshot) versions is straightforward. Reuse is promoted, and an overall smoother and more reliable project lifecycle can be the result.
So, if you’ve been considering the Maven plunge, go for it. Expect to spend a bit of time learning and experimenting, and be ready to adopt a strict and carefully thought-out versioning system for your projects. Once you’re over the hump, however, in the learning curve, the rewards are well worth it.
About the Author
Michael Nash is the president of JGlobal Limited, a software development, consulting, training and support company specializing in open source Java technologies. He is also a core developer of the Keel meta-framework, the author of two books and a number of articles and papers about next-generation web-application development with Java, and a member of the JSR-127 (JavaServer Faces) Expert Group. He can be reached at [email protected]