ZetCode

Maven version management

last modified November 9, 2026

In this article we manage versions in Maven projects. We find outdated dependencies and plugins with the Versions plugin and update them, change the version of the project, look at version ranges, and set the project version from the command line with CI-friendly versions. The examples use Maven 3.10, Java 25, Versions plugin 2.25.0, and Flatten plugin 1.9.0.

Maven itself never updates versions. A dependency declared with version 3.12.0 stays at 3.12.0 until somebody edits the POM. This makes builds reproducible, but it also means that projects fall behind on bug fixes and security patches unless we check for updates regularly.

The example project

Our project declares several dependencies and plugins with outdated versions.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>outdated</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <slf4j.version>2.0.9</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>3.6.2</version>
                <executions>
                    <execution>
                        <id>enforce-maven</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <requireMavenVersion>
                                    <version>3.9</version>
                                </requireMavenVersion>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>versions-maven-plugin</artifactId>
                <version>2.25.0</version>
            </plugin>
        </plugins>
    </build>
</project>

The SLF4J version is defined once in the slf4j.version property and used by two dependencies.

<requireMavenVersion>
    <version>3.9</version>
</requireMavenVersion>

The Enforcer plugin declares the minimum Maven version needed for the build. The Versions plugin uses it to choose plugin updates, as shown later.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>versions-maven-plugin</artifactId>
    <version>2.25.0</version>
</plugin>

The Versions plugin is declared with a version, so that its goals run with the same version on every machine. Its goals are invoked from the command line with the versions prefix.

Checking dependency updates

The display-dependency-updates goal compares the declared versions with the versions available in the remote repositories and lists the newer ones. It reads the maven-metadata.xml files of the artifacts, which contain the lists of published versions.

The report shows the newest version of each dependency. For SLF4J, it is an alpha release: the plugin does not treat pre-release versions specially, it only skips snapshots by default. Guava is offered a new major version, which may contain incompatible changes.

$ mvn versions:display-dependency-updates
[INFO] --- versions:2.25.0:display-dependency-updates (default-cli) @ outdated ---
[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   com.google.guava:guava ...................... 32.1.3-jre -> 33.8.0-jre
[INFO]   org.apache.commons:commons-lang3 .................... 3.12.0 -> 3.21.0
[INFO]   org.slf4j:slf4j-api ............................ 2.0.9 -> 2.1.0-alpha1
[INFO]   org.slf4j:slf4j-simple ......................... 2.0.9 -> 2.1.0-alpha1

Ignoring versions

The maven.version.ignore property takes a comma-separated list of regular expressions; matching versions are not considered. For permanent rules, we put the expressions into the plugin configuration.

pom.xml (Versions plugin)
    <artifactId>versions-maven-plugin</artifactId>
    <version>2.25.0</version>
    <configuration>
        <ignoredVersions>
            <ignoredVersion>.*-(alpha|beta|rc|M)[.-]?\d*</ignoredVersion>
        </ignoredVersions>
    </configuration>
</plugin>

The ignoredVersions parameter skips alpha, beta, release candidate, and milestone versions in all goals of the plugin.

In the first command, SLF4J is offered the latest stable version 2.0.20. The second command uses the configuration from the POM and also sets allowMajorUpdates to false, which limits the updates to the current major version; the Guava update disappears from the report. The allowMinorUpdates and allowIncrementalUpdates parameters restrict the updates further.

$ mvn versions:display-dependency-updates "-Dmaven.version.ignore=.*-alpha.*"
[INFO] --- versions:2.25.0:display-dependency-updates (default-cli) @ outdated ---
[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   com.google.guava:guava ...................... 32.1.3-jre -> 33.8.0-jre
[INFO]   org.apache.commons:commons-lang3 .................... 3.12.0 -> 3.21.0
[INFO]   org.slf4j:slf4j-api .................................. 2.0.9 -> 2.0.20
[INFO]   org.slf4j:slf4j-simple ............................... 2.0.9 -> 2.0.20
$ mvn versions:display-dependency-updates -DallowMajorUpdates=false
[INFO] --- versions:2.25.0:display-dependency-updates (default-cli) @ outdated ---
[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   org.apache.commons:commons-lang3 .................... 3.12.0 -> 3.21.0
[INFO]   org.slf4j:slf4j-api .................................. 2.0.9 -> 2.0.20
[INFO]   org.slf4j:slf4j-simple ............................... 2.0.9 -> 2.0.20

Checking plugin updates

The display-plugin-updates goal checks the plugins of the build. A plugin release may require a newer Maven version than the project is built with, so the goal also compares the requirements of the plugins with the minimum Maven version that the project declares.

The goal lists the available updates and confirms that all plugins have their versions specified. It found the minimum Maven version in the requireMavenVersion rule. Without the rule, the goal prints an error and a long list that groups plugin versions by the Maven version they need.

$ mvn versions:display-plugin-updates
[INFO] --- versions:2.25.0:display-plugin-updates (default-cli) @ outdated ---
[INFO] The following plugin updates are available:
[INFO]   maven-compiler-plugin ............................ 3.11.0 -> 3.16.0
[INFO]   maven-enforcer-plugin .............................. 3.6.2 -> 3.7.0
[INFO]   maven-surefire-plugin .............................. 3.1.2 -> 3.6.1
[INFO]   org.codehaus.mojo:versions-maven-plugin .......... 2.25.0 -> 2.25.1
[INFO] All plugins have a version specified.
[INFO] Project requires minimum Maven version for build of: 3.9
[INFO] Plugins require minimum Maven version of: 3.9.0
[INFO] No plugins require a newer version of Maven than specified by the pom.

Checking property updates

Versions that are defined in properties are reported separately by the display-property-updates goal. The goal finds the properties used in version elements and checks the artifacts that use them.

Both SLF4J artifacts can be updated to 2.0.20. Unlike display-dependency-updates, this goal did not offer the alpha release, although the POM has no rules for ignored versions yet.

$ mvn versions:display-property-updates
[INFO] --- versions:2.25.0:display-property-updates (default-cli) @ outdated ---
[INFO] The following version property updates are available:
[INFO]   ${slf4j.version} .................................... 2.0.9 -> 2.0.20

Updating versions

Several goals write the updates into the POM. The use-latest-releases goal replaces the versions of dependencies with the newest releases, and update-properties updates the version properties. Other goals are use-next-releases, use-latest-versions, which also considers snapshots when allowSnapshots is set, and update-parent.

The use-latest-releases goal updated Commons Lang and Guava; it leaves the versions that come from properties to update-properties. Before the first change, the plugin saved the original POM as pom.xml.versionsBackup. The versions:commit goal deletes the backup and keeps the changes; versions:revert restores the original. The backups can be turned off with -DgenerateBackupPoms=false, which is common when the project is under version control.

$ mvn versions:use-latest-releases
[INFO] Updated org.apache.commons:commons-lang3:jar:3.12.0 to version 3.21.0 in com.example:outdated:1.0.0-SNAPSHOT
[INFO] Updated com.google.guava:guava:jar:32.1.3-jre to version 33.8.0-jre in com.example:outdated:1.0.0-SNAPSHOT
[INFO] BUILD SUCCESS
$ mvn versions:update-properties
[INFO] Updated ${slf4j.version} from 2.0.9 to 2.0.20
[INFO] BUILD SUCCESS
$ ls
pom.xml
pom.xml.versionsBackup
src
$ diff pom.xml.versionsBackup pom.xml
13c13
<         <slf4j.version>2.0.9</slf4j.version>
---
>         <slf4j.version>2.0.20</slf4j.version>
20c20
<             <version>3.12.0</version>
---
>             <version>3.21.0</version>
25c25
<             <version>32.1.3-jre</version>
---
>             <version>33.8.0-jre</version>
$ mvn versions:revert
[INFO] Restoring /home/user/project/outdated/pom.xml
[INFO] BUILD SUCCESS

Setting the project version

The set goal changes the version of the project. In a multi-module project, it updates the parent POM, the modules, and the references between them.

We release version 1.0.0 and start the next development version. The nextSnapshot option increments the last number of the version and adds the -SNAPSHOT qualifier; -DremoveSnapshot removes it. The help:evaluate goal prints the resulting version.

$ mvn versions:set -DnewVersion=1.0.0
[INFO] Processing change of com.example:outdated:1.0.0-SNAPSHOT -> 1.0.0
[INFO]     Updating project com.example:outdated
[INFO]         from version 1.0.0-SNAPSHOT to 1.0.0
[INFO] BUILD SUCCESS
$ mvn versions:commit
[INFO] Accepting all changes to /home/user/project/outdated/pom.xml
[INFO] BUILD SUCCESS
$ mvn versions:set -DnextSnapshot
[INFO] SNAPSHOT found.  BEFORE 1.0.0  --> AFTER: 1.0.1-SNAPSHOT
[INFO] BUILD SUCCESS
$ mvn -q versions:commit
$ mvn -q help:evaluate -Dexpression=project.version -DforceStdout
1.0.1-SNAPSHOT

Version ranges

Instead of a fixed version, a dependency can declare a range. Maven then reads the list of available versions and takes the highest one inside the range.

Range Meaning
1.0a soft requirement for 1.0; mediation may choose another version
[1.0]exactly 1.0
[1.0,2.0]1.0 <= x <= 2.0
[1.0,2.0)1.0 <= x < 2.0
[1.0,)x >= 1.0
(,1.0]x <= 1.0
(,1.0),(1.0,)any version except 1.0

Square brackets include the bound, parentheses exclude it.

pom.xml (dependencies)
<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>[3.14,3.17)</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>[2.18,)</version>
    </dependency>
</dependencies>

Commons Lang is restricted to versions from 3.14 up to, but not including, 3.17. Commons IO takes any version from 2.18 on.

Maven resolved the ranges to 3.16.0 and 2.22.0, the newest versions available today. The same POM can resolve to other versions tomorrow, which is why ranges make builds non-reproducible; the result also depends on how recently Maven refreshed the metadata in the local repository. The resolve-ranges goal of the Versions plugin replaces the ranges with the versions they currently resolve to.

$ mvn dependency:tree
[INFO] com.example:ranges:jar:1.0.0
[INFO] +- org.apache.commons:commons-lang3:jar:3.16.0:compile
[INFO] \- commons-io:commons-io:jar:2.22.0:compile
...
$ mvn -q versions:resolve-ranges
$ diff pom.xml.versionsBackup pom.xml
15c15
<             <version>[3.14,3.17)</version>
---
>             <version>3.16.0</version>
20c20
<             <version>[2.18,)</version>
---
>             <version>2.22.0</version>

LATEST and RELEASE

Maven 2 supported the LATEST and RELEASE meta-versions, which mean the newest version and the newest release. Maven 3.10 still resolves them, but it warns that they are deprecated and that future versions may stop supporting them.

We replace the Commons IO range with LATEST. Maven prints the deprecation warning, and the tree shows the meta-version instead of a real version.

$ mvn dependency:tree
[WARNING] 'dependencies.dependency.version' for commons-io:commons-io:jar is either LATEST or RELEASE (both of them are being deprecated) @ line 20, column 22
[INFO] com.example:ranges:jar:1.0.0
[INFO] +- org.apache.commons:commons-lang3:jar:3.16.0:compile
[INFO] \- commons-io:commons-io:jar:LATEST:compile

CI-friendly versions

Release pipelines often compute the version outside Maven, for example from a Git tag or a build number. Since Maven 3.5, the project version may use three special properties: ${revision}, ${sha1}, and ${changelist}. They can be set on the command line, while the POM provides defaults.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>ci-parent</artifactId>
    <version>${revision}</version>
    <packaging>pom</packaging>

    <modules>
        <module>core</module>
    </modules>

    <properties>
        <revision>1.0.0-SNAPSHOT</revision>
        <maven.compiler.release>25</maven.compiler.release>
    </properties>
</project>

The parent POM takes its version from the revision property, which defaults to 1.0.0-SNAPSHOT.

core/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>ci-parent</artifactId>
        <version>${revision}</version>
    </parent>

    <artifactId>core</artifactId>
</project>

The module refers to its parent with the same expression. These are the only places where the version is written; no tool has to edit the POMs before a release.

The -Drevision option changes the version of the whole build. The build works, but the installed POM of the module still contains the ${revision} expression, because Maven 3 installs and deploys the POM files as they are written. Another project that depends on the module cannot resolve its parent: the property is not defined there, and Maven looks for a parent with the literal version ${revision}.

$ mvn -q help:evaluate -Dexpression=project.version -DforceStdout
1.0.0-SNAPSHOT
$ mvn -q help:evaluate -Dexpression=project.version -DforceStdout -Drevision=1.2.0
1.2.0
$ mvn -q install -Drevision=1.2.0
$ cat ~/.m2/repository/com/example/core/1.2.0/core-1.2.0.pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>ci-parent</artifactId>
        <version>${revision}</version>
    </parent>

    <artifactId>core</artifactId>
</project>
$ cd ../consumer
$ mvn compile
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal on project consumer: Could not collect dependencies for project com.example:consumer:jar:1.0.0
[ERROR] Failed to read artifact descriptor for com.example:core:jar:1.2.0
[ERROR] 	Caused by: The following artifacts could not be resolved: com.example:ci-parent:pom:${revision} (absent): prefixes: Path com/example/ci-parent/${revision}/ci-parent-${revision}.pom NOT allowed from central

Flattening the POM

The Flatten plugin fixes the problem. It generates a .flattened-pom.xml file with the expressions resolved and makes it the POM of the project, so the install and deploy steps publish the flattened file.

pom.xml (build)
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>flatten-maven-plugin</artifactId>
            <version>1.9.0</version>
            <configuration>
                <updatePomFile>true</updatePomFile>
                <flattenMode>resolveCiFriendliesOnly</flattenMode>
            </configuration>
            <executions>
                <execution>
                    <id>flatten</id>
                    <phase>process-resources</phase>
                    <goals>
                        <goal>flatten</goal>
                    </goals>
                </execution>
                <execution>
                    <id>flatten-clean</id>
                    <phase>clean</phase>
                    <goals>
                        <goal>clean</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

The resolveCiFriendliesOnly mode replaces only the three CI properties and keeps the rest of the POM as written. Other modes, such as ossrh, also remove the build sections and inline the parent, which is useful for libraries published to Maven Central. The flatten-clean execution removes the generated files in the clean phase, and the files should be ignored by version control.

The flattened POMs are installed instead of the originals and contain the real version, so the consumer builds. Maven 4 resolves these properties in the POMs it installs and deploys by itself.

$ mvn install -Drevision=1.2.0
[INFO] --- flatten:1.9.0:flatten (flatten) @ ci-parent ---
[INFO] Generating flattened POM of project com.example:ci-parent:pom:1.2.0...
[INFO] Installing /home/user/project/ci-flatten/.flattened-pom.xml
[INFO] --- flatten:1.9.0:flatten (flatten) @ core ---
[INFO] Generating flattened POM of project com.example:core:jar:1.2.0...
[INFO] Installing /home/user/project/ci-flatten/core/.flattened-pom.xml
[INFO] BUILD SUCCESS
$ cat ~/.m2/repository/com/example/core/1.2.0/core-1.2.0.pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>ci-parent</artifactId>
        <version>1.2.0</version>
    </parent>

    <artifactId>core</artifactId>
</project>
$ cd ../consumer
$ mvn dependency:tree
[INFO] com.example:consumer:jar:1.0.0
[INFO] \- com.example:core:jar:1.2.0:compile
...

Best practices

Source

Versions Maven Plugin

In this article we have found and applied version updates with the Versions plugin, used version ranges, and built a project with CI-friendly versions.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.