Maven BOM
last modified November 8, 2026
In this article we show how to use a Maven BOM (Bill of Materials) to manage dependency versions in one place. The examples use Maven 3.10 and Java 25.
A BOM is a POM file with pom packaging whose
dependencyManagement section lists dependencies with their
versions. Other projects import it and then declare those dependencies
without versions. A BOM does not add any dependency to a project; it only
decides which version is used when the project, or one of its dependencies,
needs the artifact.
BOMs bring several advantages:
- all projects and modules that import the BOM use the same versions
- a version is upgraded in one place
- libraries that consist of many modules, such as Jackson or JUnit, stay consistent, because one BOM version covers all of them
- transitive dependencies are covered as well, which reduces version conflicts
Creating a BOM
Our BOM manages the versions of Jackson, Commons Lang, and Gson.
<?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>example-bom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.23.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.15.0</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
The packaging must be pom; a BOM has no code.
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.23.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
A BOM can import other BOMs. The Jackson BOM manages all Jackson modules, so our BOM covers them without listing each one.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
Single artifacts are listed with their versions.
Other projects get the BOM from a repository, so we install it into the local
repository. Only the POM file is installed. In a company, the BOM would be
deployed to a shared repository with mvn deploy.
$ mvn install [INFO] Installing /home/user/bom/pom.xml to /home/user/.m2/repository/com/example/example-bom/1.0.0/example-bom-1.0.0.pom [INFO] BUILD SUCCESS ...
Importing the BOM
A project imports the BOM in its dependencyManagement section
with the pom type and the import scope.
<?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>app</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
The import copies the managed dependencies of the BOM into the project. The
two dependencies in the dependencies section have no versions;
they come from the BOM.
package com.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public class App {
record Library(String name, String version) {}
public static void main(String[] args) throws Exception {
var libs = List.of(
new Library("jackson-databind", ObjectMapper.class.getPackage().getImplementationVersion()),
new Library("commons-lang3", StringUtils.class.getPackage().getImplementationVersion()));
for (var lib : libs) {
System.out.println(StringUtils.rightPad(lib.name(), 20, '.') + " " + lib.version());
}
System.out.println(new ObjectMapper().writeValueAsString(libs));
}
}
The application prints the versions of the libraries it uses, read from the manifests of their JAR files, and serializes the list to JSON with Jackson.
Both the direct dependencies and the transitive Jackson modules have the
versions from the BOM. The Jackson annotations module is versioned as
2.23; since Jackson 2.20, it uses only the major and minor
version numbers.
$ mvn dependency:tree
[INFO] com.example:app:jar:1.0.0
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.23.0:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.23:compile
[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.23.0:compile
[INFO] \- org.apache.commons:commons-lang3:jar:3.21.0:compile
...
$ mvn -q compile exec:java -Dexec.mainClass=com.example.App
jackson-databind.... 2.23.0
commons-lang3....... 3.21.0
[{"name":"jackson-databind","version":"2.23.0"},{"name":"commons-lang3","version":"3.21.0"}]
Overriding a BOM version
A project can override a version from the BOM by managing the dependency itself.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
</dependencies>
</dependencyManagement>
Versions declared directly in the project's dependencyManagement
section take precedence over imported ones, regardless of their position.
A version given in the dependencies section would override the
BOM as well. Overrides should be rare and documented, since they break the
consistency the BOM provides.
$ mvn dependency:tree [INFO] com.example:override:jar:1.0.0 [INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.23.0:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.23:compile [INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.23.0:compile [INFO] \- org.apache.commons:commons-lang3:jar:3.20.0:compile ...
Several BOMs
A project can import several BOMs. If two of them manage the same artifact, the BOM imported first wins.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.20.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The project imports Jackson BOM 2.20.0 before our BOM, which brings Jackson 2.23.0. The first import wins, so all Jackson modules are at 2.20.0, while Commons Lang still comes from our BOM. This is the usual way to pin a library to a different version than a framework BOM, such as Spring Boot's, would choose: import the library's BOM before the framework's BOM.
$ mvn dependency:tree [INFO] com.example:order:jar:1.0.0 [INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.20.0:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile [INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.20.0:compile [INFO] \- org.apache.commons:commons-lang3:jar:3.21.0:compile ...
A BOM in a multi-module project
In a multi-module project, the BOM is often one of the modules, and the parent POM imports it for all modules.
<?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>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>bom</module>
<module>service</module>
</modules>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>platform-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
The parent lists the bom and service modules and
imports the BOM with ${project.version}, so the BOM and the
modules are always released together. The BOM module is a copy of our
earlier BOM named platform-bom; it must not use
parent-project as its parent, because the parent imports it,
which would create a cycle.
<?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>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>service</artifactId>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
The service module inherits the imported BOM from its parent, so its dependencies need no versions.
package com.example;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
public class Service {
record Status(String service, boolean healthy) {}
public static void main(String[] args) {
var status = new Status(StringUtils.upperCase("orders"), true);
System.out.println(new Gson().toJson(status));
}
}
A small program that uses both managed libraries.
Maven resolves the BOM from the reactor, the set of modules built together,
so it does not have to be installed first. The -pl option
selects a single module.
$ mvn package
[INFO] Reactor Summary for parent-project 1.0.0:
[INFO] platform-bom ....................................... SUCCESS [ 0.006 s]
[INFO] parent-project ..................................... SUCCESS [ 0.002 s]
[INFO] service ............................................ SUCCESS [ 4.130 s]
[INFO] BUILD SUCCESS
...
$ mvn dependency:tree -pl service
[INFO] com.example:service:jar:1.0.0
[INFO] +- com.google.code.gson:gson:jar:2.15.0:compile
[INFO] | \- com.google.errorprone:error_prone_annotations:jar:2.50.0:compile
[INFO] \- org.apache.commons:commons-lang3:jar:3.21.0:compile
...
$ mvn -q exec:java -pl service -Dexec.mainClass=com.example.Service
{"service":"ORDERS","healthy":true}
BOM or parent POM
The dependencyManagement section of a parent POM is inherited by
its children, so a parent can play the role of a BOM. However, a project has
only one parent, and the parent also passes on plugins, properties, and other
build settings. A BOM affects only dependency versions, and a project can
import any number of BOMs. Therefore, libraries publish BOMs for their users,
while parent POMs are used to share build configuration within an
organization.
Popular BOMs
Many libraries and frameworks publish a BOM. A few examples with recent versions:
| BOM | Coordinates |
|---|---|
| JUnit | org.junit:junit-bom:6.1.3 |
| Jackson 2 | com.fasterxml.jackson:jackson-bom:2.23.0 |
| Jackson 3 | tools.jackson:jackson-bom:3.3.0 |
| Spring Boot | org.springframework.boot:spring-boot-dependencies:4.1.2 |
The Spring Boot BOM manages the versions of hundreds of libraries that are tested together with Spring Boot, including Jackson, JUnit, and Logback.
Best practices
- Keep a BOM focused on a group of libraries that belong together.
- Version the BOM like any other artifact and release it whenever a managed version changes.
- Prefer importing library BOMs, such as Jackson's, over listing their modules one by one.
- Avoid overriding BOM versions; when needed, document why.
- Check the result with
mvn dependency:treeafter changing BOM imports.
Source
In this article we have shown how to create a Maven BOM and how to use it to manage dependency versions across projects and modules.
Author
List all Java tutorials.