Mastering Maven BOM Imports: Centralizing Dependency Versions Across Multi‑Module Projects
Learn how to use Maven’s dependencyManagement with BOM imports to keep dependency versions consistent, avoid drift, and troubleshoot conflicts in multi‑module builds.
05 Aug 2025, 19:41 UTC

Why BOM Imports Matter in Multi‑Module Maven Projects
When a single Maven build contains dozens of modules, each module can declare the same library with a different version. If you rely on the dependencyManagement section to centralize those versions, you still need to keep that section tidy and avoid accidental overrides. A Bill of Materials (BOM) solves this by providing a ready‑made list of managed versions that you can import once and reuse across the entire build.
What Is a BOM?
A BOM is simply a POM whose packaging is pom and whose sole purpose is to declare dependencyManagement entries. Popular libraries publish BOMs so that projects can pin all related artifacts to a single release train. For example, com.fasterxml.jackson:jackson-bom:2.15.1 manages jackson-core, jackson-databind, jackson-annotations, and many more.
Importing a BOM Inside dependencyManagement
To bring a BOM into your project, add a dependency entry inside dependencyManagement with the following attributes:
groupId,artifactId,version– the BOM coordinates.type=pom– tells Maven that the artifact is a POM.scope=import– activates the import mechanism.
Only the dependencyManagement section accepts import scope; it does not add the BOM’s dependencies to the classpath. It only pulls the version information into the current POM’s resolution context.
Practical Worked Example
Below is a minimal two‑module project that demonstrates BOM import in action.
Parent POM (pom.xml)
<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-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.15.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Notice that the BOM is declared only once, in the parent. No version is specified for the BOM’s dependencies.
Child Module A (module-a/pom.xml)
<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-SNAPSHOT</version>
</parent>
<artifactId>module-a</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<!-- No version – it will be inherited from the BOM -->
</dependency>
</dependencies>
</project>
Child Module B (module-b/pom.xml)
<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-SNAPSHOT</version>
</parent>
<artifactId>module-b</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<!-- No version – it will be inherited from the BOM -->
</dependency>
</dependencies>
</project>
Both modules reference Jackson artifacts without specifying a version. When you run mvn dependency:tree in either module, the output will show that jackson-databind and jackson-core are resolved to 2.15.1 because the BOM provided that version.
Common Pitfalls and How to Avoid Them
- Import scope only works inside
dependencyManagement– if you accidentally put the BOM import independencies, Maven will treat it as a regular dependency and try to add it to the classpath, which will fail. - Explicit versions override BOMs – if a child module declares a dependency with a
versionelement, that version wins over the BOM. Use this to override when necessary, but be aware it breaks the centralization guarantee. - Multiple BOMs managing the same artifact – Maven uses the first declaration it encounters. Declaration order in
dependencyManagementmatters. Keep BOM imports at the top of the list or use a dedicated BOM‑management parent to avoid surprises. - Transitive overrides – a BOM can also override the version of a transitive dependency. While useful for security fixes, it can cause incompatibilities if the library expects a different version. Always run
mvn dependency:treeafter adding a BOM to spot such changes.
When to Use a BOM vs. Manual Version Management
Importing a BOM is ideal when:
- You rely on a large ecosystem of libraries that publish a BOM (e.g., Spring, Hibernate, Jackson).
- All modules in a project should share the same release train for a given library.
- You want to reduce duplication of version strings in
dependencyManagement.
If you need fine‑grained control over individual artifact versions, or if your dependencies do not have a BOM, then manual entries in dependencyManagement remain the safest choice.
Actionable Checklist
- Identify a suitable BOM for the libraries you use.
- Import it once in the root
dependencyManagementsection withtype=pomandscope=import. - In child modules, declare dependencies without versions and rely on the BOM.
- Run
mvn dependency:treeormvn help:effective-pomto verify that the expected versions are in use. - When adding a new dependency, double‑check whether the BOM already manages it. If not, add a version manually or create a custom BOM.
- Keep BOM imports at the top of
dependencyManagementto avoid accidental overrides from later entries. - Use
mvn dependency:treeafter any change to spot transitive overrides or conflicts.
By following these steps, you’ll maintain a clean, predictable dependency landscape across all modules and reduce the risk of version drift or hidden conflicts.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.