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.
<?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.
<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.0 | a 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.
<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.
<?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.
<?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.
<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
- Use fixed versions for dependencies and plugins, and check for updates regularly with the Versions plugin or with a bot such as Dependabot or Renovate.
- Configure ignored versions in the POM, so that pre-releases do not show up in the reports.
- Keep versions shared by several artifacts in properties or import a BOM.
- Avoid version ranges and the
LATESTandRELEASEmeta-versions in applications; they make builds unpredictable. - When the version comes from the CI system, use
${revision}together with the Flatten plugin.
Source
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
List all Java tutorials.