The Tribe Blog

“I do not hate Apache Maven”

2056
No Comments

I recently had an opportunity to hold a few talks at DevoxxUK. This one went down as a treat, so I’d like to share a little depth in this quick blog. Thanks go out to the entire team, but some special thanks to Mark Hazell and Ellie May for organizing a really great event. Awesome work guys!

Maven-blog-andy

Apache Maven

This is a quick and simple take on ensuring that your Apache Maven based project starts and, more importantly, stays lean and clean. It’s a summary of my best practices, and I’m always happy to learn a few more.

Keep your POM file XML well formed and formatted. It should literally read like a book. Try to keep the sections consistent and in a particular order. This makes it easy to locate, read, edit and maintain.

Relative Path

I’ve often seen the parent relativePath tag misused or misunderstood. If you don’t specify it then the default of >..< for this tag is directing Maven to resolve the parent POM one level up (effectively in the parent directory). If the parent is not on your drive then use this tag to direct Maven to look it up on the central repository. The following warning is usually an indication that something is wrong:

[WARNING] ‘parent.relativePath’ of POM …

If you actually see this tag more than once in an entire project, in child POMs for example, then it’s likely to be a mistake. Your top level or uppermost POM (your project’s main POM) should only use this if it references a parent that is not on your drive. Just add with an empty or descriptive comment, like so:

Relative Path

<parent>
   <groupId>org.sonatype.oss</groupId>
   <artifactId>oss-parent</artifactId>
   <version>9</version>
   <relativePath><!--Resolve on repository--></relativePath>
</parent>

Prerequisite

Maven as a build tool is constantly evolving, which means there have been some pretty significant changes over the years. You should ensure devs are not living in the stone age. That doesn’t mean forcing them to the cutting edge, rather moving the ball a little closer.

Prerequisite

<prerequisites>
   <maven>3.2.3</maven>
</prerequisites>

modules

Expect your project to grow. I call it the ‘Hot chocolate principle’, it started with a kiss, in our case ‘a module’! Structure your project from the very beginning to contain the parent/child scheme, even if you initially only have one module. Give the parent and child project neat and descriptive names.

my-project/pom.xml

<name>MyProject</name>

<modules>
   <module>module-one</module>
   <module>module-two</module>
</modules>

my-project/module-one/pom.xml

<name>MyProject::Module One</name>

my-project/module-two/pom.xml

<name>MyProject::Module Two</name>

Using this neat layout will ensure that your project modules are easily identified in your IDE of choice.

maven.project.png

Build environment

Ensuring the build environment is consistent is critical. Use the pluginManagement section in the parent POM to declare explicit plugin versions. This will nail down the plugin environment, removing potential future conflicts. It’s still good practice to regularly check for and update to the latest versions, but don’t break the build by being overzealous.

my-project/module-two/pom.xml

<build>
   <pluginManagement>
      <plugins>
         <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
         </plugin>
      </plugins>
   </pluginManagement>
</build>

Global properties

Declare and use global properties in the parent POM for anything you think you might need to change the value of regularly. You can use these properties in placeholders anywhere within the parent and child POM files like so:

Properties

<properties>
   <!--Environment-->
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <project.build.reportEncoding>UTF-8</project.build.reportEncoding>
   <maven.compiler.target>1.7</maven.compiler.target>
   <maven.compiler.source>1.7</maven.compiler.source>

   <!--Dependency versions-->
   <version.tomee>1.7.2</version.tomee>
   <version.junit>4.11</version.junit>
</properties>

....
<version>${version.tomee}</version>
....

Also, these placeholders can be used in your project files, which can be filtered for and replaced during the build. Any property you define can be overridden from the command line using -D option:

Using the -D option

mvn clean install -Dversion.tomee=1.7.5-SNAPSHOT

This is a great way to dynamically change the build at runtime to just test a new feature, without making a permanent change to the actual POM file.

Dependency Management

Now this is where many projects fall down miserably and often need some serious attention. There really is such a thing as jar hell. You need to be aware of this and take control of it from the start. Declare and maintain the project dependency versions in the top level POM, never (or extremely rarely) in child POM files. This will ensure that all child modules will be using the same versions throughout the project and avoid conflicting libraries. If I ever see a <version> tag in a child POM then alarm bells start to ring! Listen for them.

my-project/pom.xml

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.apache.openejb</groupId>
         <artifactId>apache-tomee</artifactId>
         <version>${version.tomee}</version>
      </dependency>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>${version.junit}</version>
      </dependency>
   </dependencies>
</dependencyManagement>

When you define artifact dependencies in dependencyManagement then also be careful to never define the scope. Scope should always be defined at the point of requirement in the child POM files. Forcing a global scope on child modules is rarely a good idea. If a different scope is required then it will force the duplicated inclusion to override the global scope.

my-project/module-one/pom.xml

<dependencies>
   <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>apache-tomee</artifactId>
      <scope>compile</scope>
   </dependency>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
   </dependency>
</dependencies>

Project properties

There are numerous built-in properties that can be used to simplify refactoring. One really useful one is project. The example below uses ${project.groupId} and ${project.version} to reference that actual project. Changing these values in the parent POM will have a direct affect on the child POMs. Imagine how useful this would be if your project consisted of tens of modules or more and you wanted to change the groupId!

my-project/module-two/pom.xml

<artifactId>module-two</artifactId>
<name>MyProject :: Module Two</name>

<dependencies>
   <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>module-one</artifactId>
      <version>${project.version}</version>
   </dependency>
</dependencies>

Exclusions & Transient Dependencies

So you know there is a jar hell. There is also a transient hell. This is where artifacts declare their own dependencies, known as transient dependencies. Be meticulous in identifying and excluding these dependencies that you know you’ll never need, or at least ensuring that the actual version you require is defined explicitly. Maven uses a ‘Nearest the top of the tree’ algorithm to solve transient conflicts. So your project might pull in version 1.x of a transient dependency, when you’re actually expecting version 2.x. Be careful and constantly check and re-check for these potential transient issues.

my-project/module-two/pom.xml

<dependencies>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
      <exclusions>
         <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
         </exclusion>
      </exclusions>
   </dependency>
   <dependency>
      <artifactId>hamcrest-core</artifactId>
      <groupId>org.hamcrest</groupId>
      <version>${version.hamcrest-core}</version>
   </dependency>
</dependencies>

Run the maven dependency plugin to see a visual representation of the transient tree.

dependency:tree

mvn dependency:tree -Dverbose

Summary

These are just some of the things that I have seen cause hard to pin down issues. I hope that my suggestions will save you a headache or two, and encourage you to explore the Maven build tool. Sure there are others out there, but Maven is an extremely well established and rock hard solution when set-up correctly. It’s likely to be around for a very long time, so it’s inevitable you will encounter it at some point. Don’t fight it, and you’ll eventually come to like it, I’m sure!


Andy

About the author

Andy Gumbrecht

Senior Software Engineer
Follow Andy Gumbrecht
Andy has been fitting in tight code since getting a Sinclair ZX81 with a whopping 1k memory back in 1982. After a rewarding military career gaining many life experiences he eventually turned his long time hobby into a professional qualification, and subsequently went on to become a lead developer on several successful local government and commercial industry projects. As a senior Java developer he has never lost his love for coding, open source and best practices within the industry and has an attention to detail, performance and infrastructure. He has been using in production environments and contributing to OpenEJB since October 2009. He still dreams of his early paragliding days when it was 'really' dangerous, but these days just loves to traverse the mountains of Bavaria by foot or cycle up the occasional hill - Sometimes he even manages to convince those he loves most to drag along behind.