Maven Continuous Integration Best Practices

By

4 minute read time

Continuous integration (CI) is a development best practice that you need to use in your process. It is an essential part of an efficient software development life cycle (SLDC).

If you aren't using it already, then you should start, now. The main benefit of continuous integration is the ability to flag errors as they are introduced into a system, instead of waiting multiple days for test failures and critical errors to be identified during the QA cycle. This post isn't about the virtues of using CI, it's about how to setup an optimal environment in a Maven shop. Here are seven tips for running Maven builds in a CI system, such as Hudson.

Automate Snapshot Deployment

In my experience, it is best to let your CI system deploy your snapshots. This is the most reliable way to ensure that the contents of your repository are kept in sync with your source control system. To do this practically, you need to couple CI with a repository manager like Sonatype Nexus Repository that can automatically purge snapshots. I've managed projects that produced >300 GB of snapshots in less than a week. Using a repository manager will save your sanity.

Isolate Local Repositories

Another critical component of a good CI setup is local repository isolation. The local repository in Maven is the temporary holding spot for all artifacts downloaded and produced by Maven, and it is not currently setup to be multi-process safe. There is a remote possibility of a conflict, but it exists.

The main reason I like to have a local repository per project is that it's the only way to test that your project is build-able against the artifacts in the corporate repository. If you don't have separate local repos, the product on one build will be seen by another build on CI, even if it's not in the corporate repository. This is important, since one function of CI should be to validate that the code is buildable by a real developer.

Tip: use -Dmaven.repo.local=xxxx to define the unique local repositories for each build.

Regularly Purge Local Repositories

To further validate the contents of the repository, and to manage the disk space, I purge the local repositories every night. This way, if changes in the repository or artifacts are removed, the CI system will detect this. To keep it easy to purge all the local repositories, I tend to structure them under a single common folder, such as /opt/repos/*.

Obviously, having many local repositories requires more disk space than a single monolithic one due to dependency duplication, but even on our large grid, the repos are less than 10 GB. Local repos get giant when you don't control the snapshots, and purging them nightly keeps this under control.

Tip: use your CI system itself to schedule the local repo cleanup. This way anyone can manually clean the repos from the UI if Maven gets confused.

Over time, I've also picked up a few more simple tricks:

Enable Batch Mode

Tip: Enable -B (batch) mode on the build. This will make the logs shorter, since it avoids the dependency download progress logging. It also ensures the build won't hang due to waiting for user input. (to enable globally in settings.xml:<interactiveMode>false</interactiveMode>)

Enable Full Stack Traces

Tip: Enable -e to cause Maven to produce the full stack trace if there's a build exception. This will make it easier to comprehend any problems in the resulting build failure log/email without having to build it again.

Print Test Failures to Standard Output

Tip: Enable -Dsurefire.useFile=false. This is a favorite of mine, since surefire prints test failures to standard out, where it will be included in the build failure log and email. This saves you from having to dig back onto the machine to find the surefire report, just to see a simple stack trace. (to enable globally in settings.xml:<properties><surefire.useFile>true</surefire.useFile></properties> in an active profile)

Always Check for Snapshots

Tip: Enable -U to cause Maven to always check for new snapshots. (to enable globally in settings.xml: <updatePolicy>always</updatePolicy>....this goes on a repository definition)

Summary

Using the above settings and process causes every build to push the artifacts to the repository. The next downstream build will have its own clean repo and check the repository manager for the latest snapshots. Then at least once a day, everything is dumped locally, and all dependencies are pulled out of the repository manager.

Naturally, doing all this updating and purging puts some network load between the CI and repository manager. This works best if they share a high-speed network. If your repository manager isn't close to your CI system, you should put one there, if only to proxy the artifacts and reduce the impact of the daily local repo purge.

Note: If you follow these tips, it is essential that you download a copy of Nexus. Purging the contents of your local repository and downloading everything from the Central Maven repository once a day (per project) is exactly the sort of behavior that causes traffic problems on the Central Maven repository.

Picture of Brian Fox

Written by Brian Fox

Brian Fox, CTO and co-founder of Sonatype, is a Governing Board Member for the Open Source Security Foundation (OpenSSF), a Governing Board Member for the Fintech Open Source Foundation (FINOS), a member of the Monetary Authority of Singapore Cyber and Technology Resilience Experts (CTREX) Panel, a ...

Tags