dependencyManagement and BOMs

Maven · Practical · 12 min

This is how Spring Boot and large projects keep hundreds of dependency versions consistent.

<dependencyManagement> declares versions (and scopes, exclusions) without adding the dependencies. Modules or the <dependencies> section then list only groupId and artifactId, inheriting the managed version. Managed versions also apply to transitive dependencies, so declaring jackson-databind there pins the version wherever it appears in the graph.

A BOM (bill of materials) is a POM with <packaging>pom</packaging> containing only a dependencyManagement section. Import it with scope import:

<dependencyManagement><dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>3.4.4</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>
</dependencies></dependencyManagement>

After that, Spring, Jackson, JUnit and dozens of other libraries can be declared without versions and are guaranteed to be mutually compatible. Spring Boot projects more often inherit from spring-boot-starter-parent, which imports this BOM and also configures plugins; importing the BOM is the alternative when you need your own parent.

Precedence: a version declared directly in <dependencies> beats a managed one; among managed versions, the first declaration in the POM (including the order of imported BOMs) wins. Use mvn help:effective-pom to see the final result and mvn dependency:tree -Dverbose to see which versions were managed.

Example

<dependencyManagement>
  <dependencies>
    <dependency>                                       <!-- import a BOM -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>3.4.4</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>                                       <!-- pin a version everywhere -->
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>33.4.0-jre</version>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>                                         <!-- versions come from management -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
  </dependency>
</dependencies>

Remember

  • dependencyManagement sets versions without adding dependencies
  • Managed versions also override transitive versions
  • Import a BOM with type pom and scope import
  • Direct versions beat managed ones; first managed declaration wins

Check yourself

  1. What happens if a dependency is listed in dependencyManagement but not in dependencies?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#dependency-management