Blog

Keeping dependencies up-to-date in Maven

20 May, 2016
Xebia Background Header Wave

code { display: inline !important;
background-color: #ffffff;
font-weight: bold !important;
border:0px !important;
font-size:14px !important;
}
.syntaxhighlighter {
font-size: 120% !important;
}

Keeping your dependencies up-to-date is more important than ever in modern projects. Everything is connected to the internet and needs to be secure. New vulnerabilities in libraries are found, exploited and patched within days. We use a lot of dependencies, and due to continuous delivery some of your dependencies will need updating every day. Solid dependency management is a primary requirement for good software.
In this blog post I will describe how to keep a Maven project up-to-date with the versions plugin. Using the versions plugin has a lot of benefits and configuring it takes less than an hour. So I suggest using it for all maven projects.

The maven goal that achieves this is:
[bash light="true"]
mvn versions:display-dependency-updates
[/bash]
By default, it will generate a list on the command prompt of all your outdated dependencies. But it will contain false positives and to use it in a continuous integration environment, more configuration is required.
This example assumes a typical maven project, with a parent pom that configures all dependencies using the maven dependency management mechanism. The plugin configuration I used is:
[xml]
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<outputFile>outdated-dependencies.txt</outputFile>
<rulesUri>file:///${session.executionRootDirectory}/maven-version-rules.xml</rulesUri>
</configuration>
</plugin>
[/xml]
Here the <outputFile> contains the file where the results are written to. <inherited>false</inherited> stops the plugin from running on your child projects. If all your dependencies are defined in the parent pom this will only clutter the results.
The most interesting setting is the <rulesUri> tag. This tag refers to a file where you can make rules defining what is considered an update.
Sometimes the versions plugin suggests a dependency update that you do not want. Most projects only use stable libraries, and avoid various Alpha, Beta, Release Candidate and Milestone versions. There are also several odd version numbers out there, which mess up the plugin. For example, commons-collections has a version number 20031027.000000, released in 2005. You probably want version 3.2.2, released in 2015. However, the plugin will suggest 20031027.000000, because 20031027 is bigger than 3.
Finally, sometimes you do not want to update a dependency because of an incompatible API or because it is not worth the effort to upgrade.
To accommodate this, you need comparison rules. These are described in an XML file and tell the plugin what versions to pick. You can find the description here: https://www.mojohaus.org/versions-maven-plugin/rule.html.
For our project I used the following file:
[xml]
<?xml version="1.0" encoding="UTF-8"?>
<ruleset xmlns="https://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" comparisonMethod="maven" xsi:schemaLocation="https://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0 https://mojo.codehaus.org/versions-maven-plugin/xsd/rule-2.0.0.xsd”&gt;
<ignoreVersions>
<!– Ignore Alpha’s, Beta’s, release candidates and milestones –>
<ignoreVersion type="regex">(?i).Alpha(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).
Beta(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).-B(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).
RC(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).CR(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).
M(?:-?\d+)?</ignoreVersion>
</ignoreVersions>
<rules>
<!– Obvious mismatches –>
<rule groupId="commons-collections" artifactId="commons-collections">
<ignoreVersions>
<ignoreVersion type="regex">^200.$</ignoreVersion>
</ignoreVersions>
</rule>
<rule groupId="commons-logging" artifactId="commons-logging">
<ignoreVersions>
<ignoreVersion>99.0-does-not-exist</ignoreVersion>
</ignoreVersions>
</rule>
<rule groupId="org.hamcrest">
<ignoreVersions>
<ignoreVersion>1.4-atlassian-1</ignoreVersion>
</ignoreVersions>
</rule>
<!– Version 5 of hibernate requires javax.validation 1.1.0. This is a new api and a lot of effort to upgrade –>
<rule groupId="org.hibernate" artifactId="hibernate-validator" comparisonMethod="maven">
<ignoreVersions>
<ignoreVersion type="regex">^5.
$</ignoreVersion>
</ignoreVersions>
</rule>
<rule groupId="javax.validation" artifactId="validation-api">
<ignoreVersions>
<ignoreVersion type="regex">1.1.0.Final</ignoreVersion>
</ignoreVersions>
</rule>
</rules>
</ruleset>
[/xml]
As you can see, I commented the choices I made when selecting versions:
I threw out all non-stable releases. I solved some odd versioning in commons-collections and commons-logging. I ignored the Atlassian specific version of Hamcrest. I also disabled Hibernate 5, because 4 works fine for us and we do not want to use the new API.
Note that this file is a very good place to document these dependency decisions.
You can now setup Jenkins to run this regularly (we run it once every Sunday) and mail the generated file outdated-dependencies.txt. This will make it easy to keep your projects up-to-date. Feel free to use our rules file as a starting point for your project.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts