I resisted Maven for a very long time. It seemed to be extremely complex for no great benefit. I thoroughly understood Ant and had built extensive and complex scripts that worked quite well.
But, eventually, I saw the light. I saw how trivial it made the majority of builds. I saw how it integrated with IDEs and Continuous Integration servers. And how all of those tasks I’d built in Ant were no longer necessary. But I also realized that Maven was still a mystery to many developers and that the vast majority of the documentation I found was preaching to the choir. So, I decided to provide a very basic guide to Maven.
Maven builds are controlled by Project Object Model (POM) files. POM files, unlike Ant scripts, describe the project. A POM file doesn’t have code or task steps, it just describes the structure of the project. It’s up to Maven to take that description and use it to build the project.
Maven makes a lot of assumptions. Just about the simplest POM file you could have might look like this:
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0-SNAPSHOT</version> </project>
This little file would compile Java code, copy resources, generate a documentation web site and assemble a JAR file. What does it say exactly?
- modelVersion - The Maven POM version number. It’s the version number of the POM specification
- groupId - The project’s organizational group
- artifactId - The name of the artifact the project creates
- version - The version number of the artifact created
Maven manages dependencies between projects. If your project needs a jar file like Log4J, you’ll add that dependency to your POM file and Maven will automatically fetch that jar file and make it available when the code is compiled.
That dependency tracking works for your own project components as well. That’s why Maven needs you to define component names that are globally unique. So, in our above example, the final JAR file for our project would be ‘my-project.jar’ but it would be registered with Maven as com.mycompany.my-project.
The version number means something to Maven as well. Maven tracks multiple versions of each component. So if you need Log4J version 1.2, you ask for it explicitly.
When your own code is compiled and stored by Maven, it also has to have a version number. Version numbers with ‘SNAPSHOT’ in them are special. They’re “work-in-progress” code. Maven breaks components into ‘releases’ and ‘snapshots’. It won’t allow you to store components that have snapshot version numbers as releases.
How does Maven know how to build your code from just that little project file? It makes even more assumptions. It assumes that your project directory structure looks like this:
/src /main /java - All your Java source files, e.g., /com/mycompany/Main.java /resources - Resource files that need to end up in the project JAR /webapp - Web files if you’re building a WAR /test /java - All your test Java source files /resources - Resource files needed for testing /target - Everything generated by the build, include the JAR/WAR file
There are additional directories. All of them together are known as the Standard Directory Layout.
If your source code follows that directory structure, then Maven will know how to compile it, unit test it and package it. If it doesn’t, then you’ll have to tell Maven how your directory structure differs.
Let’s say that your project stores Java source code in ‘/src/java’ and puts the Java unit test code in ‘/src/test’. Then your POM file would look like this:
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0-SNAPSHOT</version> <build> <sourceDirectory>src/java</sourceDirectory> <testSourceDirectory>src/test</testSourceDirectory> </build> </project>
With just these few elements, Maven supports many “goals”. These goals are akin to Ant tasks and describe what you want Maven to do. Some common goals:
- clean - remove any old build files
- compile - compile the code
- test - compile the code and run the unit tests
- package - compile and test the code and create any build products
- site - generate site documentation
Maven components are stored in repositories. You can define as many repositories as you like, but you can’t install your own JAR files or other build products in the default Maven repositories. You have to create and host your own.
Each developer also has their own “local” repository. The local repository is just a set of directories on each developer’s machine (located under ‘.m2’ in the user’s home directory). When Maven downloads components from repositories they are stored locally. Developers can also install components directly into the local repository.
When you start adding dependencies to your POM files, Maven will attempt to locate them in one of it’s repositories and then download them to the local repository.
Let’s look at a POM file with a dependency.
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> </dependency> </dependencies> </project>
This POM file will cause Maven to download the Log4J 1.2.13 component and make it available on the classpath while compiling the code. If you were creating a WAR file, EAR file, etc. it would also copy the jar file into the ‘lib’ directory of the final package. You can add as many dependencies as you like and, optionally, give them a scope. The scope tells Maven whether the dependency is only necessary when testing the code or only necessary to compile but not for packaging.
When you’re working in a team, you need to be able to share your custom components between team members. That’s when you setup a private repository. The easiest way is to install Sonatype Nexus, an open source repository. After it’s installed, you define which public repositories you want to use then and create a ‘release’ and a ‘snapshot’ repository.
Once you’ve created your private repositories, each developer will edit their Maven settings file (.m2/settings.xml):
<?xml version=“1.0” encoding=“UTF-8”?> <settings> <mirrors> <mirror> <id>nexus-public-snapshots</id> <name>Nexus Public Snapshots Mirror</name> <mirrorOf>public-snapshots</mirrorOf> <url>http://maven.mycompany.com:8081/nexus/content/groups/public-snapshots</url> </mirror> <mirror> <id>nexus</id> <name>Nexus Public Mirror</name> <mirrorOf>*</mirrorOf> <url>http://maven.mycompany.com:8081/nexus/content/groups/public</url> </mirror> </mirrors> </settings>
Now, Maven will always look in your private repository for components. Your private repository should include “proxies” for any public Maven repositories you want to use.
Once this is done, you can tell Maven to automatically install built components into your private repository when you execute the ‘deploy’ goal. You tell Maven where the components should go with ‘distributionManagement’ definitions.
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> </dependency> </dependencies> <distributionManagement> <repository> <id>releases</id> <name>Internal Releases</name> <url>http://maven.mycompany.com:8081/nexus/content/repositories/releases</url> </repository> <snapshotRepository> <id>snapshots</id> <name>Internal Snapshots</name> <url>http://maven.mycompany.com:8081/nexus/content/repositories/snapshots</url> </snapshotRepository> </distributionManagement> </project>
Maven will automatically choose between the ‘release’ and ‘snapshot’ repository based on the project version number.
There are many additional features and functions built-in to Maven. But Maven is also extensible. It will download components “on-the-fly” as you reference them in your POM files. Maven plug-ins are used for everything from deciding which Java language-level to use, to whether Javadocs should be generated.
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.5</version> </plugin> </plugins> </reporting> </project>
In the above POM file, a ‘build’ plug-in describes the source and target Java language level for the project. A ‘reporting’ plug-in adds Javadoc generation. ‘Build’ plug-ins modify the compile and package phases. ‘Reporting’ plug-ins modify the output generated by the ‘site’ goal.
Once you understand all of the basic concepts of Maven and how they are used, it becomes much simpler to use the Maven documentation and to locate pre-existing Maven components. The easiest way to find dependencies is to search for them on MVNrepository. Once you find the appropriate one you can choose from the list of available versions and it will display the section to include in your POM file:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency>
And now when you look at the Maven plug-ins, the documentation will make much more sense.
Categories: JavaApache Maven, Continuous Integration, Java, Maven POM, Software Dependency Management, XML