Keep dependencies up to date

Despite the guidance to minimize dependencies, it is inevitable that most projects will have multiple dependencies. It is good practice to always work to keep these libraries up to date. Updating to newer versions protects against security vulnerabilities, and often brings new features, performance improvements, and bug fixes. Tracking dependencies manually can be quite burdensome, so fortunately there are tools that make our lives easier.

Before we begin, it is worth reiterating that the burden of dependency versioning should ideally be limited to BOM files only, as this drastically reduces the number of dependency versions we actually care about in our project. When we update a BOM, all of the referenced dependencies within that BOM are updated to newer versions, with the added benefit that we can have a higher degree of confidence in the transitive closure of all dependencies, which should result in greater compatibility and lower likelihood of diamond dependency problems.

With that out of the way, we can now decide to investigate our dependencies locally, or defer it to automated services. We will cover both below.

Build tooling

Maven

There are two great tools available to developers who use Maven. Firstly, there is the ability to list out the entire dependency tree of any project by calling mvn dependency:tree. This will output a tree such as the following:

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.azure:azure-core >------------------------
[INFO] Building Microsoft Azure Java Core Library 1.20.0-beta.1
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ azure-core ---
[INFO] com.azure:azure-core:jar:1.20.0-beta.1
[INFO] +- com.fasterxml.jackson.core:jackson-annotations:jar:2.12.4:compile
[INFO] +- com.fasterxml.jackson.core:jackson-core:jar:2.12.4:compile
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.12.4:compile
[INFO] +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.12.4:compile
[INFO] +- com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.12.4:compile
[INFO] |  +- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.12.4:compile
[INFO] |  |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.2:compile
[INFO] |  |  - jakarta.activation:jakarta.activation-api:jar:1.2.1:compile
[INFO] |  +- org.codehaus.woodstox:stax2-api:jar:4.2.1:compile
[INFO] |  - com.fasterxml.woodstox:woodstox-core:jar:6.2.4:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.7.32:compile
[INFO] +- com.google.code.findbugs:jsr305:jar:3.0.2:provided
[INFO] +- io.projectreactor:reactor-core:jar:3.4.8:compile
[INFO] |  - org.reactivestreams:reactive-streams:jar:1.0.3:compile
[INFO] +- io.netty:netty-tcnative-boringssl-static:jar:2.0.40.Final:compile
[INFO] +- io.projectreactor:reactor-test:jar:3.4.8:test
[INFO] +- org.junit.jupiter:junit-jupiter-api:jar:5.7.2:test
[INFO] |  +- org.apiguardian:apiguardian-api:jar:1.1.0:test
[INFO] |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] |  - org.junit.platform:junit-platform-commons:jar:1.7.2:test
[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.7.2:test
[INFO] |  - org.junit.platform:junit-platform-engine:jar:1.7.2:test
[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.7.2:test
[INFO] +- org.hamcrest:hamcrest-library:jar:2.2:test
[INFO] |  - org.hamcrest:hamcrest-core:jar:2.2:test
[INFO] |     - org.hamcrest:hamcrest:jar:2.2:test
[INFO] +- org.mockito:mockito-core:jar:3.9.0:test
[INFO] |  +- net.bytebuddy:byte-buddy:jar:1.10.20:test
[INFO] |  +- net.bytebuddy:byte-buddy-agent:jar:1.10.20:test
[INFO] |  - org.objenesis:objenesis:jar:3.2:test
[INFO] +- com.github.tomakehurst:wiremock-standalone:jar:2.24.1:test
[INFO] +- org.openjdk.jmh:jmh-core:jar:1.22:test
[INFO] |  +- net.sf.jopt-simple:jopt-simple:jar:4.6:test
[INFO] |  - org.apache.commons:commons-math3:jar:3.2:test
[INFO] +- org.openjdk.jmh:jmh-generator-annprocess:jar:1.22:test
[INFO] +- javax.json:javax.json-api:jar:1.1.4:test
[INFO] - org.jacoco:org.jacoco.agent:jar:runtime:0.8.5:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.171 s
[INFO] Finished at: 2021-08-30T09:15:20+12:00
[INFO] ------------------------------------------------------------------------

This makes use of the Apache Maven Dependency Plugin. Clicking that link will take you to the website for this plugin where you'll note there are many other very useful goals available as part of this plugin. One of the more useful goals in this plugin is dependency:analyze, which "analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused and declared."

The second plugin available to Maven users is the Versions Maven Plugin. With this plugin we can check for available updates, and even have those updates applied directly into the appropriate place in our POM files. For starters, it is recommended to run mvn versions:display-dependency-updates, as this will scan a project's dependencies and produce a report of those dependencies which have newer versions available. From there we have various commands available to us that determine which of these dependencies should be updated. The best approach to take is context-specific, but this tutorial details the options well.

Automated services

As you can see, running the commands above is trivial, but it still requires someone to be proactive and willing to check for new dependency releases. Fortunately, there are also tools out there that can provide similar kinds of guidance, even providing pull requests into your code repository with the updated dependency. Two such tools are Dependabot from GitHub, and Snyk. After configuring these services to scan your source repositories automatically, you'll be notified by your source repository with new incoming pull requests when updates are available.