How to bring dependencies up to date with Maven

I come to bury this task, not to praise Maven.

This post is just some pointers on how, assuming you’ve a maven project, to bring its dependencies up to date. Maven has some good tools for doing this and that’s what we’ll cover.

Preparing the pom

One thing that makes maintaining dependency versions in maven simpler is to use properties to define them.

What you’ll often find in a pom.xml is something like:

 <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>14.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
 </dependencies>

The more dependencies you have the more and more difficult finding each ones version gets. Since the versions are the most frequent thing to change a simple solution is to use properties:

 <properties>
        <junit.version>4.10</junit.version>
        <quava.version>14.0</quava.version>
 </properties>
 <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
 </dependencies>

Finding What’s Out of Date

Finding candidates for upgrade is in fact simple:

     mvn versions:display-dependency-updates

Will yield, amongst it chatter the following:

[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   com.google.guava:guava .................................. 14.0 -> 18.0
[INFO]   junit:junit ...................................... 4.10 -> 4.12-beta-2

So from this output you can often simply update the properties you’ve extracted:

 <properties>
        <junit.version>4.12-beta-2</junit.version>
        <quava.version>18.0</quava.version>
 </properties>

But you’ll note here JUnit’s newest is a beta, so that you may want to look for the last stable version and use that. If you go to the central repository’s advanced search you can look for GroupId junit, ArtifactId junit and then click the Lastest Version all and look at the version history. Maybe 4.11 is more your risk level? Some projects changed versioning schemes so you’ll get things like 20131111 -> 4.1 and at that point looking at the version history may be the only way to determine chronological order.

Don’t Trust, Verify

Simply setting the the version of a dependency does not guarantee that version is used. That may seem like a bug, but in fact maven is doing it’s level best to reconcile versions of your dependencies, and their dependencies and so on. The process and thinking on this is outside the scope of this post, but will cover some simple tactics for dealing with this.

First, as said, don’t trust, verify:

mvn dependency:tree -Dverbose

This will yield the tree of dependencies as maven sees it:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ example ---
[INFO] com.exampl:example:jar:1.0-SNAPSHOT
[INFO] +- com.google.guava:guava:jar:18.0:compile
[INFO] +- junit:junit:jar:4.11:test
[INFO]   \- org.hamcrest:hamcrest-core:jar:1.3:test

So in this example, you got what you had expected. Done. However, there will be times when you’ll find that a version you indicated is not being used, and this should always relate to the dependency appearing multiple times in the tree with different versions and maven selecting another version than the one you want.

Coming to an Agreement

So how can you get maven to go with the version you want? You need get the dependency tree to agree with you and the two tactics here are both pretty straight forward.

If you own it, Upgrade it

If the dependency is another artifact you manage, first upgrade it there. Lather, rinse, repeat.

Exclude it

You can also tell maven not to recursively descend into parts of the tree using exclusions.
For example suppose you wanted a newer hamcrest-core then 1.3:

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

This will tell maven not to bring the artifact in from there. With this in place your dependency should win the argument. Mind you, there are times where you just can’t win. If the artifact has significantly changed and the developer hasn’t maintain backwards compatibility you may not be able to find a version that works for both (XML parsers used to be notorious for this).

Keeping Watch

This process is not automatic, it involves some work, and always taking the latest and greatest, while it may avoid tech debt has real risks of its own, so when do you do it? That is a matter of preference, but here is something to at least help you keep an eye on change. Add the following to your pom.xml‘s plugins:

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>versions-maven-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <id>display-dependency-updates</id>
                        <goals>
                            <goal>display-dependency-updates</goal>
                        </goals>
                        <phase>verify</phase>
                    </execution>
                </executions>
            </plugin>

With that in place every time you do a mvn install you’ll get a report of the updates available to you and you can decide when you want to act on it.

Maybe you don’t need a newer version at all

If this plugin was reliable, I’d have noted it as a first step, but as it is I’ll mention it only as a footnote. Running:

mvn dependency:analyze

Will analyze your dependencies and inform you of those that you don’t even need in the pom at all. This happens in the normal course of life, you add a dependency and then refactor the code and the orphan the dependency. The problem is this plugin yields a lot of false positives. I’ll run it, and then try commenting out each dependency I think are in fact now not needed, and then install to see if it was true.

And if You Want it Automagic

Maven also allows ranges in the version numbers. This will allow you to say things like any version greater than X. This can be useful, but it does mean that each time you build the exact same project you might get a different suite of dependencies. So it you’re looking for control and reproducibility this isn’t the way to go.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s