skip to content

How does Spring Boot keep all the libraries a starter pulls version-aligned? Explain the role of spring-boot-starter-parent vs importing the BOM.

level: middleimportance: must knowfreq 70%

answer

  1. spring-boot-dependencies = the BOM
  2. dependencyManagement pins versions, adds no jars
  3. parent POM vs import scope=import type=pom
  4. Override via version property
  5. Explicit version beats BOM

basics

~20 s

Versions live in the spring-boot-dependencies BOM — a dependencyManagement list of managed versions. You inherit it either by using spring-boot-starter-parent as your Maven parent, or by importing spring-boot-dependencies directly (Gradle's Boot plugin does this for you). Starters then omit versions.

solid answer

~40 s

The alignment comes from a **Bill of Materials (BOM)** named `spring-boot-dependencies` — a POM with a large `<dependencyManagement>` block that pins a compatible version for every library in the ecosystem. Starters declare their transitive deps **without versions**; resolution fills them from the BOM, so every starter agrees on shared libraries. You consume the BOM two ways: (1) **`spring-boot-starter-parent`** — set it as your `<parent>`; it inherits from `spring-boot-dependencies` and adds plugin config, resource filtering, and Java defaults. (2) **BOM import** — when you already have a corporate parent, add `spring-boot-dependencies` to your own `<dependencyManagement>` with `<scope>import</scope>` and `<type>pom</type>`. Gradle uses the Spring Boot plugin (which applies dependency management importing the same BOM) or `platform(...)`. To override a version, redeclare the managed property (e.g. `<jackson.version>`); the BOM exposes such properties.

code

xml · 26 lines
xml
<!-- Option B: keep a corporate parent, import Boot's BOM for versions -->
<parent>
  <groupId>com.acme</groupId>
  <artifactId>acme-corp-parent</artifactId>
  <version>9.2.0</version>
</parent>

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

<dependencies>
  <!-- no <version> needed: resolved from the imported BOM -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

go deeper

for a junior

Know that versions come from a BOM so you don't type them.

for a middle

Explain spring-boot-dependencies BOM, parent vs import-scope, and how starters omit versions.

for a senior

Discuss overriding managed versions safely, corporate-parent scenarios, and Gradle's platform/plugin equivalents.

for a principal

Govern multi-BOM composition (Boot + Cloud), enforce alignment across many services, and weigh the risk of local version overrides against the tested matrix.

## Where versions actually come from A starter like `spring-boot-starter-web` lists dependencies (spring-webmvc, tomcat-embed, jackson-databind…) but typically **omits their `<version>`**. Something has to supply those versions. That something is **dependency management** driven by the **`spring-boot-dependencies` BOM**. ### What a BOM is A **Bill of Materials** is a POM used purely for its `<dependencyManagement>` section. It does not add dependencies to your build; it **declares versions** that take effect *if and when* a dependency is actually pulled. `spring-boot-dependencies` pins hundreds of coordinates (Spring, Hibernate, Jackson, Tomcat, Logback, testcontainers, etc.) at versions Spring's team tested together. ## Two ways to consume it (Maven) ### 1. Parent POM ```xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.1</version> </parent> ``` `spring-boot-starter-parent` **itself inherits from** `spring-boot-dependencies`, so you get all managed versions **plus** extras: sensible plugin management (the Boot Maven plugin, surefire), UTF-8/Java version defaults, resource filtering of `application.properties`, and property-driven overrides. ### 2. Import scope (when you already have a parent) Many companies have a mandated corporate parent POM, and Maven allows only one `<parent>`. Import the BOM instead: ```xml <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.4.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ``` This gives version management **without** the parent's plugin/defaults conveniences (you configure those yourself). ## Gradle Gradle has no POM inheritance. The **Spring Boot Gradle plugin** (`org.springframework.boot`) together with `io.spring.dependency-management`, or Gradle's native `platform()`/`enforcedPlatform()`, imports the same BOM: ```kotlin plugins { id("org.springframework.boot") version "3.4.1" } // Boot plugin imports the spring-boot-dependencies BOM automatically, // so implementation("...:spring-boot-starter-web") needs no version. ``` ## Overriding a managed version Because the BOM exposes version **properties**, you override by redefining the property (with the parent): ```xml <properties> <jackson-bom.version>2.17.2</jackson-bom.version> </properties> ``` In Gradle: `extra["jackson.version"] = "2.17.2"` (via the dependency-management plugin) or a `resolutionStrategy`. **Gotcha:** overriding one library's version can break the tested compatibility matrix — do it deliberately and test. ## Key gotchas - The BOM only sets versions for coordinates you actually depend on; it doesn't add jars. - `import` scope pulls **only** dependencyManagement, never the parent's plugin config. - If you declare an explicit `<version>` on a dependency, it **wins** over the BOM (and silently escapes the alignment guarantee). - Mixing two BOMs (e.g., Spring Cloud + Spring Boot) requires importing them in the right order; nearest/first declaration wins.

  • When would you import the BOM instead of using spring-boot-starter-parent?
    When your project already has a mandatory parent POM (e.g., a corporate parent). Maven allows only one <parent>, so you import spring-boot-dependencies with scope=import to still get version alignment, and configure Boot's Maven plugin yourself.
  • How do you override the version of a single library managed by the BOM, and what's the risk?
    Redefine its version property (e.g. <jackson-bom.version> with the parent, or the equivalent in Gradle's dependency-management). Risk: you leave Spring's tested compatibility matrix, so an incompatible combination can cause subtle runtime/classpath failures — override deliberately and test.

saying these in an interview costs you the question

  • Saying the BOM adds dependencies to the build (it only manages versions)
  • Claiming import scope gives you the parent's plugin/resource-filtering conveniences
  • Not knowing an explicit <version> overrides the BOM and silently breaks alignment

context