Maven parent POM vs BOM
last modified November 13, 2026
In this article we compare two ways of sharing dependency versions between Maven projects: inheriting from a parent POM and importing a bill of materials (BOM). We build the same application with each of them, look at what each one shares, and combine both in the setup that most organizations use. The examples use Maven 3.10 and Java 25.
Both a parent POM and a BOM are POM files with pom packaging,
and both usually contain a dependencyManagement section. They
differ in how a project uses them. A project inherits from a
parent: the parent's POM is merged into the project's POM. A project
imports a BOM: only the dependencyManagement section of
the BOM is copied into the project's own dependencyManagement.
The Maven POM and Maven BOM
articles cover the basics of both.
The parent POM
Our organization, ACME, uses two Apache Commons libraries. The first version of its shared configuration is a parent 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>
<groupId>com.example</groupId>
<artifactId>acme-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<commons-lang3.version>3.21.0</commons-lang3.version>
<commons-text.version>1.15.0</commons-text.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<archive>
<manifestEntries>
<Built-By>ACME</Built-By>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
The parent manages the versions of Commons Lang and Commons Text through properties, sets the Java release, adds the SLF4J API to every project, and configures the JAR plugin.
<properties>
<maven.compiler.release>25</maven.compiler.release>
<commons-lang3.version>3.21.0</commons-lang3.version>
<commons-text.version>1.15.0</commons-text.version>
</properties>
Properties are inherited. A child project gets the Java release and can change a managed version by redefining the property.
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
Dependencies in the dependencies section of a parent are
inherited too. Every child gets SLF4J, whether it needs it or not.
<manifestEntries>
<Built-By>ACME</Built-By>
</manifestEntries>
The build section, with plugin versions and configuration, is
inherited as well. Every JAR built by a child carries the
Built-By manifest entry.
Inheriting from the parent
An application refers to the parent with the parent element.
<?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>acme-parent</artifactId>
<version>1.0.0</version>
<relativePath/>
</parent>
<artifactId>app-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
</dependencies>
</project>
The empty relativePath element tells Maven not to look for the
parent in the parent directory; it is resolved from the repositories. We
install the parent into the local repository with mvn install
first. The Commons Text dependency has no version; it comes from the
inherited dependency management.
package com.example.app;
import org.apache.commons.text.WordUtils;
public class Main {
public static void main(String[] args) {
System.out.println(WordUtils.capitalizeFully("an old falcon"));
}
}
The application capitalizes words with Commons Text. The same class is used in all projects of this article.
The tree contains Commons Text 1.15.0, Commons Lang 3.21.0, which the
parent manages for the transitive dependency, and SLF4J, which comes from
the parent although the application does not use it. Redefining the
commons-text.version property changes the managed version. On
the command line we use -D; in a real project we would add the
property to the properties section of the child POM. The
manifest of the packaged JAR contains the Built-By entry from
the inherited JAR plugin configuration.
$ mvn dependency:tree com.example:app-parent:jar:1.0.0-SNAPSHOT +- org.apache.commons:commons-text:jar:1.15.0:compile | \- org.apache.commons:commons-lang3:jar:3.21.0:compile \- org.slf4j:slf4j-api:jar:2.0.17:compile $ mvn dependency:tree -Dcommons-text.version=1.14.0 +- org.apache.commons:commons-text:jar:1.14.0:compile $ unzip -p target/app-parent-1.0.0-SNAPSHOT.jar META-INF/MANIFEST.MF Manifest-Version: 1.0 Created-By: Maven JAR Plugin 3.5.1 Java-Version: 25 Build-Jdk-Spec: 25 Built-By: ACME
The BOM
The second version of the shared configuration is a BOM. It contains only the dependency 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>acme-bom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<commons-lang3.version>3.21.0</commons-lang3.version>
<commons-text.version>1.15.0</commons-text.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
The BOM manages the same two libraries with the same properties. It has no dependencies, no plugins, and no Java release; a BOM is meant to provide versions and nothing else.
Importing the BOM
The application imports the BOM in its dependencyManagement
section.
<?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-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>acme-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
</dependencies>
</project>
The import needs the pom type and the import
scope. The application is not tied to a parent, so it sets the Java release
itself.
<dependency>
<groupId>com.example</groupId>
<artifactId>acme-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Maven reads the BOM, interpolates its properties in the context of the BOM, and copies the resulting managed dependencies into the project. Nothing else is copied.
The tree has the same Commons versions, but no SLF4J. Setting
commons-text.version has no effect now, because the BOM's
version expressions were resolved when it was imported; the property does
not even exist in the project. To change a version that comes from a BOM,
the project must manage the dependency itself, as shown below.
$ mvn dependency:tree com.example:app-bom:jar:1.0.0-SNAPSHOT \- org.apache.commons:commons-text:jar:1.15.0:compile \- org.apache.commons:commons-lang3:jar:3.21.0:compile $ mvn dependency:tree -Dcommons-text.version=1.14.0 \- org.apache.commons:commons-text:jar:1.15.0:compile $ mvn help:evaluate -Dexpression=commons-text.version -q -DforceStdout null object or invalid expression
What is shared
The following table summarizes what a project gets from each of them.
| POM section | Parent POM | Imported BOM |
|---|---|---|
dependencyManagement | inherited | imported |
dependencies | inherited | ignored |
properties | inherited, can be overridden | ignored |
build, pluginManagement | inherited | ignored |
repositories, profiles | inherited | ignored |
| group ID and version | inherited | ignored |
| number per project | one | any |
The last row is the main practical difference. A project can have only one
parent, but it can import as many BOMs as it needs. Projects that already
have a parent, such as Spring Boot applications that inherit from
spring-boot-starter-parent, can still take versions from a
company BOM.
Combining both
In practice, the two are combined. The BOM manages dependency versions and can be used by anyone, including projects with a different parent. A platform parent POM imports the BOM and adds the build configuration: the Java release and the plugin 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>acme-platform-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>acme-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
The parent has no managed dependencies of its own; it imports them from the BOM. Children inherit the imported entries together with the rest of the dependency management.
<?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>acme-platform-parent</artifactId>
<version>1.0.0</version>
<relativePath/>
</parent>
<artifactId>app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
The application inherits from the platform parent, imports the JUnit BOM, and overrides one version.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
A version declared directly in dependencyManagement wins over
the versions imported from BOMs, wherever they come from. This is the way to
change a single version that a BOM provides.
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
The project imports a second BOM in addition to the one imported by the parent. When two BOMs manage the same artifact, the first import wins.
package com.example.app;
import org.apache.commons.text.WordUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class MainTest {
@Test
void capitalizes() {
assertEquals("An Old Falcon", WordUtils.capitalizeFully("an old falcon"));
}
}
A JUnit test checks the capitalization.
Commons Text comes from the ACME BOM through the parent, Commons Lang is the locally overridden 3.20.0, and the JUnit artifacts come from the JUnit BOM. Surefire runs in the version from the parent's plugin management.
$ mvn test dependency:tree
--- surefire:3.6.1:test (default-test) @ app ---
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: ... s -- in com.example.app.MainTest
com.example:app:jar:1.0.0-SNAPSHOT
+- org.apache.commons:commons-text:jar:1.15.0:compile
| \- org.apache.commons:commons-lang3:jar:3.20.0:compile
\- org.junit.jupiter:junit-jupiter:jar:6.1.3:test
+- org.junit.jupiter:junit-jupiter-api:jar:6.1.3:test
| +- org.opentest4j:opentest4j:jar:1.3.0:test
| +- org.junit.platform:junit-platform-commons:jar:6.1.3:test
| +- org.apiguardian:apiguardian-api:jar:1.1.2:test
| \- org.jspecify:jspecify:jar:1.0.0:test
+- org.junit.jupiter:junit-jupiter-params:jar:6.1.3:test
\- org.junit.jupiter:junit-jupiter-engine:jar:6.1.3:test
\- org.junit.platform:junit-platform-engine:jar:6.1.3:test
BUILD SUCCESS
When to use which
- Use a BOM to publish a set of versions that work together, especially for a library or framework that consists of several artifacts.
- Use a parent POM to share build configuration: the Java release, plugin versions and configuration, repositories, and profiles.
- Do not put dependencies in the
dependenciessection of a shared parent; every child gets them. - Keep the dependency versions in a BOM and import it in the parent, so that projects which cannot use the parent can still use the versions.
- Override a single BOM version with a direct entry in
dependencyManagement, not with a property.
Source
Introduction to the dependency mechanism
In this article we have compared a parent POM with an imported BOM and combined both in a platform parent that imports a BOM.
Author
List all Java tutorials.