DEV Community

Eduardo Issao Ito
Eduardo Issao Ito

Posted on

Check for newer versions of dependencies in pom.xml

I made a script in Groovy language to look for newer versions of all declared dependencies in a Maven project. The script supports multi-module projects.

It searches the current folder for pom.xml files and checks the central.sonatype.com website for a newer versions. Only dependencies that have an explicit version defined in pom will be checked. Versions defined by properties will be resolved if the properties were declared in one of the pom files. (If a property is defined more than once, the result is undefined.)

Example of use:

check-versions.sh example

I recommend installing Java 17 (or newer) and Groovy 4 with sdkman!

Put these two files in a folder that is included in PATH.

check-versions.sh:

#!/usr/bin/env bash
BASEDIR=$(unset CDPATH; cd `dirname $0`; pwd)

JAVA_VER=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}')
if [ "$JAVA_VER" -lt 170 ]; then
    echo "Java 17 necessario"
    exit 1
fi

#$PROXY="-DproxySet=true -DproxyHost=10.0.0.24 -DproxyPort=8080"

groovy -Dgroovy.grape.report.downloads=true $PROXY $BASEDIR/check-versions.groovy 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

check-versions.groovy:

import groovy.json.JsonSlurper
import groovy.xml.XmlParser
@Grab('org.jodd:jodd-lagarto:6.0.6')
import jodd.jerry.Jerry
@Grab('org.fusesource.jansi:jansi:2.4.1')
import org.fusesource.jansi.AnsiConsole
@Grab('org.slf4j:slf4j-jdk-platform-logging:2.0.13')
import org.slf4j.Logger

import static org.fusesource.jansi.Ansi.Color.GREEN
import static org.fusesource.jansi.Ansi.Color.RED
import static org.fusesource.jansi.Ansi.ansi

AnsiConsole.systemInstall()

record Dependency(String groupId, String artifactId, String version) {}

static def findAllDependencies() {
    //println("-- findAllDependencies")
    def allDeps = []
    def mvnProperties = [:]
    new File(".").traverse(type: groovy.io.FileType.FILES, nameFilter: ~/pom.xml/) {
        processPom(new XmlParser().parse(it), allDeps, mvnProperties)
    }
    return allDeps.unique().sort { k1, k2 -> k1.groupId + k1.artifactId <=> k2.groupId + k2.artifactId }
}

static def processPom(def project, def allDeps, def mvnProperties) {
    collectProperties(project, mvnProperties)
    collectDependencies(project.'**'.dependencies.dependency, allDeps, mvnProperties)
    collectDependencies(project.parent, allDeps, mvnProperties)
    collectDependencies(project.'**'.plugin, allDeps, mvnProperties)
}

static def collectProperties(def project, def mvnProperties) {
    //println("-- collectProperties")
    for (def p : project.properties) {
        for (def c : p.children()) {
            def name = c.name().localPart
            def value = c.text()
            mvnProperties[name] = value
        }
    }
}

static def collectDependencies(def dependencies, def allDeps, def mvnProperties) {
    //println("-- collectDependencies")
    for (def d : dependencies) {
        def groupId = d.groupId.text()
        if (groupId.isEmpty()) {
            // assume default groupId for maven plugins
            groupId = "org.apache.maven.plugins"
        }
        def artifactId = d.artifactId.text()
        def version = d.version.text()
        if (version.isEmpty())
            continue
        if (version.startsWith("\$")) {
            def matcher = (version =~ /\$\{(.+?)\}/)
            if (matcher.find()) {
                def property = matcher.group(1)
                version = mvnProperties[property]
                if (version == null) {
                    continue
                }
            }
        }
        def x = new Dependency(groupId, artifactId, version)
        allDeps.add(x)
    }
}

/**
 * This method is highly dependable on the html format returned by central.sonatype.com
 *
 * Return this map:
 * [name:logstash-logback-encoder, namespace:net.logstash.logback, version:7.4, versions:[7.4, 7.3, 7.2, 7.1.1, 7.1, 7.0.1, 7.0, 6.6, 6.5, 6.4, 6.3, 6.2, 6.1, 6.0, 5.3, 5.2, 5.1, 5.0, 4.11, 4.10], tags:[], usedIn:1600, tab:versions]
 */
def findLatestVersion(String groupId, String artifactId) {
    //println("-- findLatestVersion ${groupId}:${artifactId}")
    def url = "https://central.sonatype.com/artifact/${groupId}/${artifactId}/versions"
    def body = url.toURL().getText()
    def doc = Jerry.of(body)
    def scripts = doc.find('html.nx-html.nx-html--page-scrolling body.nx-body script')

    def text = ""
    scripts.each {
        def element ->
            if (element.text().contains('self.__next_f.push')) {
                text = element.text()
            }
    }

    int i = text.indexOf(':')
    int j = text.lastIndexOf('"')
    text = text.substring(i + 1, j - 2)
    text = text.replaceAll('\\\\"', '"')
    text = text.replaceAll('\\\\"', '"')

    def json = new JsonSlurper().parseText(text)
    try {
        def result = json[0][3].children[0][3]
        return result
    }
    catch (Exception ignored) {
        return null
    }
}

//skipVersions = [:]
skipVersions = [
        "org.springframework.boot:spring-boot-starter-parent"  : "3.2",
        "org.springframework.boot:spring-boot-starter-data-jpa": "3.2",
        "org.springframework.boot:spring-boot-starter-web"     : "3.2",
        "org.springframework.boot:spring-boot-dependencies"    : "3.2",
        "org.springframework.cloud:spring-cloud-dependencies"  : "2023",
]

def getLatestVersion(String groupId, String artifactId) {
    //println("-- getLatestVersion")
    def versions = findLatestVersion(groupId, artifactId)
    def skip = skipVersions["${groupId}:${artifactId}"]
    if (skip == null) {
        return versions?.version
    }

    for (def v in versions.versions) {
        if (v.startsWith(skip)) {
            continue
        }
        return v
    }
}

def allDeps = findAllDependencies()

for (def d : allDeps) {
    def latest = getLatestVersion(d.groupId, d.artifactId)
    if (latest == null) {
        continue
    }

    print("${d.groupId}:${d.artifactId}:${d.version} ")

    if (d.version.equals(latest)) {
        println("${ansi().fg(GREEN)}[OK]${ansi().reset()}")
    } else {
        println("${ansi().fg(RED)}[${latest}]${ansi().reset()}")
    }
}
Enter fullscreen mode Exit fullscreen mode

By changing the skipVersions variable, it is possible to ignore a version of some dependencies.

For example: I'm stuck with Spring Boot 3.1, so the config below will ignore version 3.2, but will still notify for newer 3.1.x version.

skipVersions = [
        "org.springframework.boot:spring-boot-starter-parent"  : "3.2",
        "org.springframework.boot:spring-boot-starter-data-jpa": "3.2",
        "org.springframework.boot:spring-boot-starter-web"     : "3.2",
        "org.springframework.boot:spring-boot-dependencies"    : "3.2",
        "org.springframework.cloud:spring-cloud-dependencies"  : "2023",
]
Enter fullscreen mode Exit fullscreen mode

If there is no dependency to skip, just use an empty map:

skipVersions = [:]
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
khmarbaise profile image
Karl Heinz Marbaise

I recommend to use the versions-maven-plugin like this:

mvn versions:display-dependency-updates
Enter fullscreen mode Exit fullscreen mode

In particular related to using spring boot including the bom file you can configure the versions-maven-plugin to limit the output on the bom updates instead of the deps updates itself...
mojohaus.org/versions/versions-mav...

Collapse
 
adzubla profile image
Eduardo Issao Ito

These have slightly different behavior.

The plugin will complain about all spring-boot dependencies, even if only spring-boot-starter-parent have a explicit version declared:

[INFO]   org.springframework.boot:spring-boot-configuration-processor ...
[INFO]                                                           2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-devtools ......... 2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-starter-actuator ...
[INFO]                                                           2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-starter-aop ...... 2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-starter-test ..... 2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-starter-validation ...
[INFO]                                                           2.7.3 -> 3.2.5
[INFO]   org.springframework.boot:spring-boot-starter-web ...... 2.7.3 -> 3.2.5
[INFO]   org.springframework.data:spring-data-mongodb .......... 3.4.2 -> 4.2.5
Enter fullscreen mode Exit fullscreen mode

My script will only report on version differences if a dependency has a version explicitly declared in pom files. Only the spring-boot-starter-parent would be reported:

org.springframework.boot:spring-boot-starter-parent:2.7.3 [3.2.5]
Enter fullscreen mode Exit fullscreen mode
Collapse
 
khmarbaise profile image
Karl Heinz Marbaise

The issue is that you used the plugin based on no configuration... instead of correctly configuring it:

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <configuration>
          <processDependencyManagement>true</processDependencyManagement>
          <processDependencyManagementTransitive>false</processDependencyManagementTransitive>
          <processDependencies>false</processDependencies>
          <processPluginDependencies>false</processPluginDependencies>
          <processPluginDependenciesInPluginManagement>false</processPluginDependenciesInPluginManagement>
        </configuration>
        <executions>
          <execution>
            <id>bommajor</id>
            <goals>
              <goal>display-dependency-updates</goal>
            </goals>
            <configuration>
              <allowMajorUpdates>true</allowMajorUpdates>
              <allowMinorUpdates>true</allowMinorUpdates>
              <allowIncrementalUpdates>true</allowIncrementalUpdates>
            </configuration>
          </execution>
          <execution>
            <id>bomminor</id>
            <goals>
              <goal>display-dependency-updates</goal>
            </goals>
            <configuration>
              <allowMajorUpdates>false</allowMajorUpdates>
              <allowMinorUpdates>true</allowMinorUpdates>
              <allowIncrementalUpdates>true</allowIncrementalUpdates>
            </configuration>
          </execution>
          <execution>
            <id>bompatch</id>
            <goals>
              <goal>display-dependency-updates</goal>
            </goals>
            <configuration>
              <allowMajorUpdates>false</allowMajorUpdates>
              <allowMinorUpdates>false</allowMinorUpdates>
              <allowIncrementalUpdates>true</allowIncrementalUpdates>
            </configuration>
          </execution>
        </executions>
      </plugin>
Enter fullscreen mode Exit fullscreen mode

Based on the above configuration you can now call:

mvn versions:display-dependency-updates -N -ntp
Enter fullscreen mode Exit fullscreen mode

That will printout only the updates for the bom's only ... or direct dependencies:

$> mvn versions:display-dependency-updates -N -ntp
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------< com.soebes.spring.example:demo-project >---------------
[INFO] Building Employee Demo Application 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- versions:2.16.2:display-dependency-updates (default-cli) @ demo-project ---
[INFO] The following dependencies in Dependency Management have newer versions:
[INFO]   nl.jqno.equalsverifier:equalsverifier ............... 3.14.1 -> 3.16.1
[INFO]   org.assertj:assertj-bom ............................. 3.24.2 -> 3.25.3
[INFO]   org.junit:junit-bom ............................... 5.9.3 -> 5.11.0-M2
[INFO]   org.mockito:mockito-bom .............................. 5.3.1 -> 5.12.0
[INFO]   org.springframework.boot:spring-boot-dependencies ...
[INFO]                                                       3.0.6 -> 3.3.0-RC1
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.425 s
[INFO] Finished at: 2024-05-18T15:59:39+02:00
[INFO] ------------------------------------------------------------------------
$> mvn versions:display-dependency-updates@bomminor -N -ntp
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------< com.soebes.spring.example:demo-project >---------------
[INFO] Building Employee Demo Application 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- versions:2.16.2:display-dependency-updates (bomminor) @ demo-project ---
[INFO] The following dependencies in Dependency Management have newer versions:
[INFO]   nl.jqno.equalsverifier:equalsverifier ............... 3.14.1 -> 3.16.1
[INFO]   org.assertj:assertj-bom ............................. 3.24.2 -> 3.25.3
[INFO]   org.junit:junit-bom ............................... 5.9.3 -> 5.11.0-M2
[INFO]   org.mockito:mockito-bom .............................. 5.3.1 -> 5.12.0
[INFO]   org.springframework.boot:spring-boot-dependencies ...
[INFO]                                                       3.0.6 -> 3.3.0-RC1
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.422 s
[INFO] Finished at: 2024-05-18T15:59:51+02:00
[INFO] ------------------------------------------------------------------------
$> mvn versions:display-dependency-updates@bompatch -N -ntp
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------< com.soebes.spring.example:demo-project >---------------
[INFO] Building Employee Demo Application 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- versions:2.16.2:display-dependency-updates (bompatch) @ demo-project ---
[INFO] Assuming allowMajorUpdates false because allowMinorUpdates is false.
[INFO] Assuming allowMajorUpdates false because allowMinorUpdates is false.
[INFO] Assuming allowMajorUpdates false because allowMinorUpdates is false.
[INFO] Assuming allowMajorUpdates false because allowMinorUpdates is false.
[INFO] Assuming allowMajorUpdates false because allowMinorUpdates is false.
[INFO] The following dependencies in Dependency Management have newer versions:
[INFO]   nl.jqno.equalsverifier:equalsverifier ............... 3.14.1 -> 3.14.3
[INFO]   org.springframework.boot:spring-boot-dependencies .... 3.0.6 -> 3.0.13
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.437 s
[INFO] Finished at: 2024-05-18T15:59:59+02:00
[INFO] ------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode