devxlogo

Apache Maven Simplifies the Java Build Process—Even More Than Ant

Apache Maven Simplifies the Java Build Process—Even More Than Ant

pache Maven, a potential replacement for the Java-based Apache Ant build tool, promises to eliminate the hassle of maintaining complicated build scripts. This new tool (along with a new approach) can automate the Java build process. Just give Maven a bit of information about your project’s directory structure and it handles the rest. All of the functionality required to build your project (clean, compile, copy resources, etc.) is built right into Maven. In other words, for standard builds you don’t need to create a custom build script at all.

In this article, I’ll explain exactly what Maven is and which problems it attempts to solve. Then, I’ll walk you through automating your own build process using Maven. As Ant currently is the most popular build tool in the Java arena, this article also assumes the reader has a basic understanding of it.

The Build Process Automates Daily Tasks
As programmers, we spend most of our time automating tasks for others. But what about automating tasks for ourselves? IDEs automate much of our work, but many developers look to an external tool for one particular task: the build process. The build concept has evolved over the years, and build tools today automate just about any task that a developer may encounter while developing, compiling, testing, or deploying software.

For my work, I am grudgingly using a general-purpose, scripting language more and more with the build tool?not unlike the macro languages built into IDEs. Table 1 shows a few of the build functions that I perform daily.

Table 1. Daily Build Functions I Perform
CompileRun jikes or javac
CopyCopy resource files into the classes directory
CleanDelete the classes directory so that our next compile will be a clean compile
Delete any temporary, app-server-generated files
Recreate the classes directory
JarZip up a project in to a .jar file
JavadocGenerate documentation from source code
TestUse JUnit to run the project’s unit tests
DeployCopy runtime files to a staging server
KodoRun Kodo’s JDO byte-code enhancer (Kodo is a JDO implementation that post-processes class files to make them persistence-capable.)

Move Over Ant, Maven Is Here
When I first started using Ant to automate the above-mentioned tasks, it was a big timesaver. Especially once I learned to use and create custom Ant Tasks. But that was for individual, fairly simple projects. I now use Ant to automate build tasks for multiple, complex projects. A considerable part of my day is spent creating and maintaining complex Ant scripts. Over time, maintaining these scripts became a major thorn in my side.

Specifically, the following Ant limitations led me to start looking for a new build tool:

  1. Cross-project reuse. My 10 Ant build scripts are mostly the same. So I started looking for code reuse features in Ant. As it turns out, Ant has no convenient way to reuse targets across projects. Copy-and-paste is the only choice.
  2. Cross-developer reuse. Most of my Ant scripts are complex but not particularly unique to my project. In other words, since most of my build scripts perform the same functions as other people’s build scripts, why should I have to create a build script at all?other than for project-specific functionality?
  3. Logic and looping. As mentioned previously, I now use Ant for general-purpose scripting. Therefore, I need general-purpose scripting features, like conditional logic, looping constructs, and reuse mechanisms. Ant was never meant to be a general-purpose scripting tool, however. As such, although possible via the Script Task or a custom Task, adding conditional logic and looping to the Ant build process is awkward.

To be fair, Ant does allow you to create custom Tasks, which are reusable across projects. These are a great help and probably are the reason for Ant’s success. However, even with reusable Tasks, you still end up with numerous, mostly redundant Targets in each project. If your objective is to simplify or eliminate the build script, you need something more. You need reusable Target Libraries. That’s where Maven comes in.

Maven addresses these issues. With this new tool, targets (which Maven calls goals) are reusable (See the sidebar “Maven Term and Concept Summary” for a complete listing of Maven terminology.). In fact, for the most common tasks, Maven has already created the goals. This means you don’t have to create them yourself. For simple projects, you may not need a build file at all. (See the sidebar “Maven Versus Ant” for a comparative analysis of Ant and Maven.)

Maven + POM = Goals Achieved
To achieve its magic, Maven uses a Project Object Model (POM), which describes a project in the form of an XML file, project.xml. Specifically, it describes the project’s directory structure, its jar file dependencies, and some of its project management details. Think of the POM as project meta-data. Once you describe your project to Maven by creating the POM, you can invoke any of Maven’s built-in goals (remember, a goal is like an Ant target).

The following sections demonstrate Maven. They walk you step by step through creating a simple project and invoking some of the Maven-provided goals.

Install Maven
To install Maven:

  1. Download the latest archive from the Maven Web site and unzip it.
  2. Set the MAVEN_HOME environment variable.
  3. Add /bin to your PATH environment variable.

Keep in mind that the first time you use Maven to build a project it will download a bunch of jar files from the Net. These jar files are needed by Maven’s plethora of plug-ins (which I describe later). So you may have time to get a cup of coffee while waiting.

Create a Simple Maven Project
Take the following the steps to create a simple Maven project and run a few Maven goals.

  1. Create the project directory:

    c:daven

  2. Create a directory for java source code:

    c:davensrcjava

  3. Create directories for your package structure (smartsoft.daven.*):

    c:davensrcjavasmartsoftdaven

  4. Create a simple class called Box:

    File name:

    c:davensrcjavasmartsoftdavenBox.java

    Content:

    package smartsoft.daven;public class Box {    private int length;    private int width;    public Box(int length, int width) {        this.length = length;        this.width = width;    }    public int getArea(){        return length * width;    }}
  5. Create a minimal POM:

    File name:

    C:davenproject.xml

    Content:

        daven    1.0                        ${basedir}/src/java            

Now, run one of Maven’s built-in goals, java:compile. Open a command prompt, switch into your project directory (C:daven), and type:

maven java:compile

Your screen output should look like this:

C:daven>maven java:compilejava:prepare-filesystem:java:compile:    [echo] Compiling to C:daven/target/classesBUILD SUCCESSFULTotal time:  33 seconds

Note that the java:compile goal has, as a prerequisite, the java:prepare-filesystem goal. Maven’s prerequisite is analogous to Ant’s depends.

When you look at your file system, you’ll see that Maven created an output directory structure for your class files and placed the compiled class in it. It also created some directories related to unit testing, which I will discuss shortly. Here is what your directory structure looks like now:

c:daven    src        java            smartsoft                daven                    Box.java    target        classes            smartsoft                daven                    Box.class        test-classes        test-reports 	

Try a few more goals. At the command prompt, type:

maven jar

Your screen output should look like this:

C:daven>maven jarjava:prepare-filesystem:java:compile:    [echo] Compiling to C:daven/target/classesjava:jar-resources:test:prepare-filesystem:test:test-resources:test:compile:    [echo] No test source files to compiletest:test:    [echo] No tests to runBUILD SUCCESSFULTotal time:  6 seconds

Note that the jar goal has a number of other goals as prerequisites. Namely:

java:prepare-filesystemjava:compilejava:jar-resourcestest:prepare-filesystemtest:test-resourcestest:compiletest:test

Also, note that Maven created a jar file in the target directory called daven-1.0.jar. It derived the file name from the projectId and currentVersion elements in your project.xml file. Now try the clean goal. At the command prompt, type:

maven clean

Your screen output should look like this:

C:daven>maven cleanclean:clean:    [delete] Deleting directory C:daven	argetBUILD SUCCESSFULTotal time:  7 seconds

If you examine your file system, you’ll see that the target folder has been deleted.

Understanding Goals
You’ve now seen three of Maven’s goals: java:compile, jar, and clean. Remember that although Maven goals are the equivalent of Ant Targets, you don’t need to create the goals yourself. Maven provides most of the goals you’ll need. As of this writing, Maven shipped about 312 goals.

Also keep in mind that Maven’s goals are organized into plug-ins. In fact, the Maven Web site describes the tool as a “small core that works with a satellite of plug-ins.” Most of Maven’s functionality comes in the form of plug-ins. Plug-ins even provide the prefixes in the goal names. For example, java:compile refers to the compile goal of the java plug-in. And kodo:enhance refers to the enhance goal of the kodo plug-in. If you run a plug-in without a goal name, you’ll invoke the default goal for that plug-in. For example, maven jar invokes the jar plug-in’s default goal.

Integrate Unit Testing
Unless automated unit testing is cleanly integrated into the build process, developers tend to skip it. Maven eliminates any excuses for not testing by making it easy. All you have to do is tell Maven where your test classes are. Here are the steps:

  1. In project.xml, add the unitTestSourceDirectory element directly below the sourceDirectory element:
    ...    ${basedir}/src/test...
  2. Create a test case called BoxTest:

    File name:

    c:davensrcjavasmartsoftdavenBoxTest.java

    Content:

    package smartsoft.daven;import junit.framework.TestCase;public class BoxTest extends TestCase{    public void test1(){        Box b = new Box(2,2);        assertEquals(4,b.getArea());    }}
  3. Run the java:jar goal again, which indirectly calls the test:compile and test:test goals:
    maven java:jar

The console output should look like this:

C:daven>maven jarjava:prepare-filesystem:java:compile:    [echo] Compiling to C:daven/target/classesjava:jar-resources:test:prepare-filesystem:test:test-resources:test:compile:    [javac] Compiling 1 source file to C:daven	arget	est-classestest:test:    [junit] dir attribute ignored if running same VM    [junit] Running smartsoft.daven.BoxTest    [junit] Tests run: 1, Failures: 0, Errors: 0BUILD SUCCESSFULTotal time:  10 seconds

If any tests failed, you can look at the generated test reports in the directory targets/test-reports.

Maven’s Repository Organizes Jar Files
Most projects use third-party libraries in the form of jar files. The repository is a special Maven mechanism for organizing the jar files and other dependencies that your builds use. (Maven also uses the term artifact to refer to dependencies.) The repository essentially is a folder with a Maven-specified structure, and it solves two problems. First, it provides a central location for all the jar files and other build dependencies you need to build your projects. Second, it helps deal with version issues by proposing a naming convention. The following is the structure of a Maven repository:

/repositoryjdojars			kodo-1.1.jar	kodo-1.2.jar			jdoInterfaces-1.0.jarfiltersjars			jxFilter-1.0.jar	jxFilter-1.1.jar

Or, more generally:

/repository/groupId/type/artifactId-version.ext

Thus, for kodo-1.1.jar, jdo is the groupId, jar is the type of dependency, kodo is the artifactId, and 1.1 is the version. These elements will be represented in your POM.

Maven supports a local repository and a shared network repository. It first checks the local repository, and if it doesn’t find the jar file, it checks the shared repository. Maven caches jar files from the shared directory in the local directory.

Determining Project Dependencies
Maven’s repository feature is closely related to the way Maven deals with project dependencies. In Ant, you have to create a classpath to indicate which jar files your project depends on, while Maven uses the dependencies element in project.xml for that purpose.

Add a jar file dependency to your project to see how this works. Add the following block of code to project.xml, below the currentVersion element but above the build element:

...1.0            jdo        kodo        2.5.2    ...

In order for this tag to work, the following jar file must exist:

/repository/jdo/jars/kodo-2.5.2.jar

Multiple goals use the dependency tag. For example, the compile goal uses it to generate the classpath, and the deploy goal uses it to determine which jar files to copy.

Protect Your Resources
Class files are usually loaded from the classpath. Non-class files (resources) can be loaded from the classpath as well, using the various getResource methods. If you want to keep these resources from getting wiped out every time you invoke the clean goal, you need to store resources outside of the classes directory and copy them into the classes directory as part of the build process. Maven has a goal for this purpose called jar-resources. It doesn’t jar anything, it just copies resources to the target/classes directory. Maven automatically calls the jar-resources goal when you run the jar goal.

First, you need to tell Maven where your resource files are:

  1. Add the resources element, as shown below, to project.xml:
    ...                                            ${basedir}/src/conf                                    ...
  2. Place a resource file, say box.properties, in /src/conf.
  3. Run the jar goal, which indirectly runs the jar-resources goal:
    maven jar

Look in the target/classes folder. You should see that Maven copied box.properties from src/conf to target/classes.

Creating Goals and Plug-ins
Like Ant, Maven allows you to create your own goals. You can utilize Maven goals at varying levels:

  1. Use an existing goal as is.
  2. Extend an existing goal.
  3. Create your own project-level goal.
  4. Create your own cross-project goal by creating a Maven plug-in.

I discuss each of these in the following sections.

Project-specific Goals
Project-specific goals are defined in a file called maven.xml, which is roughly analogous to Ant’s build.xml. Here are the steps to create a project-specific goal in Maven:

  1. Create a file called maven.xml in your project root:

    c:davenmaven.xml

  2. Add the following XML snippet:
                        Hello Maven    
  3. Invoke the goal:
    maven myGoal-1

As you can see, with maven.xml, Maven can act just like Ant. (By the way, mkdir is an Ant task.) Maven can execute any Ant task, and it also supports most of the JSTL tags, which provide basic logic and looping functionality. For example, if you wanted to create a goal that makes five directories, you could use the following:

                    ${i}    

The maven.xml file is actually a Jelly Script. Jelly is a general-purpose, XML-based scripting language. Maven uses it, but it’s not specific to Maven. (See the sidebar “Jelly: Executable XML” for more information.)

Extending a Goal
You can extend an existing goal in a project-specific way by defining a preGoal and/or a postGoal element in maven.xml. For example, if you’re happy with Maven’s built-in clean goal but you would also like Maven to clear your application server’s temporary cache, you could extend clean with a postGoal. My application server, Caucho Resin, uses a directory called WEB-INF/work for it’s temporary files. Try adding the following block of code to maven.xml:

        Deleted Resin work directory

Then rerun the clean goal:

maven clean

Cross-project Goals (Plug-ins)
Maven allows you to create generic, reusable goals, which in my opinion are what make Maven so useful. By comparison, Ant supports reusable tasks, but not reusable targets. To create a reusable goal, you need to create a Maven plug-in.

As an example, I use Solarmetric’s Kodo, a JDO compliant object/relational mapping tool. JDO is a great technology but it adds an extra step to the build process. Specifically, you have to run a separate task that adds persistence functionality to your class files (JDO refers to this as byte-code enhancement). Luckily, you can call the byte-code enhancer from a Maven goal. And since I use Kodo in multiple projects, it’s a great candidate for a cross-project goal, or in other words, a plug-in.

For the sake of simplicity, start with a slightly less ambitious “hello world” plug-in. Take the following steps to create a minimal Maven plug-in:

  1. Create a directory for the plug-in:

    .mavenpluginsmaven-hello-plugin-1.0

  2. Create the plugin.jelly file:

    File name:

    .mavenpluginsmaven-hello-plugin-1.0plugin.jelly

    Content:

                Hello Maven Plug-in!    
  3. Create a minimal project.xml file inside the plug-in’s folder:

    File name:

    .mavenpluginsmaven-hello-plugin-1.0project.xml

    Content:

    This project.xml file obviously adds no value to your plug-in, and logically you don’t need it. But Maven will return an error message if you don’t provide it. More sophisticated plug-ins will make use of this file to extend the POM with plug-in-specific settings.

  4. Invoke the new plug-in:
    maven hello

You’ve completed your first plug-in!

More Than a Build Tool
Although I’ve described and primarily used Maven as a build tool, it is actually more than just that. The Maven Web site describes Maven as “a Java project-management and project-comprehension tool.” For example, the POM (project.xml) has tags to specify the developers’ names, the developers’ e-mail addresses, the project inception date, etc. Goals such as site:generate will then use that information to auto-generate a project Web site to document your project.

Remember, the power of Maven is in its plug-ins. So spend some time exploring the many plug-ins and goals that it provides. For if you have to maintain multiple, large build scripts and you’re tired of copy-and-paste reuse, Maven and its long list of plug-ins will make your life easier.

Acknowledgements
Dave Ford thanks Jason van Zyl (Maven’s architect) and his associates, Ron Hitchens, Dave Solum, John Sheehan, and Jordan Fisher, for contributing valuable feedback for this article.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist