Safeguard
Security

The Spring Boot BOM: A Security-First Guide to Version Management

The Spring Boot BOM pins hundreds of transitive versions for you. Used well it closes CVEs fast; used carelessly it hides an end-of-life framework in your build.

Priya Mehta
Security Analyst
6 min read

The Spring Boot BOM is the spring-boot-dependencies bill of materials that pins compatible versions for hundreds of libraries at once, and treating it as a security control rather than a convenience is what separates a patched application from a breached one. When a CVE lands in Jackson, Tomcat, or Logback, the version you actually ship is usually the one the Spring Boot BOM chose, not one you set by hand. So understanding how the BOM resolves versions, and how to override them safely, is core to Java supply chain hygiene.

What the Spring Boot BOM is

A BOM (bill of materials) is a special POM whose only job is to declare managed dependency versions in its dependencyManagement section. Spring publishes one called spring-boot-dependencies. It does not add any code to your classpath; it just says "if you use Jackson, use this version; if you use Tomcat, use that version," across the whole curated set that Spring tests together.

Most projects never reference it directly. They inherit from spring-boot-starter-parent, which in turn imports spring-boot-dependencies. That is why you can write a dependency with no <version> element and Maven still resolves one:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.1</version>
</parent>

<dependencies>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <!-- no version: the BOM supplies it -->
  </dependency>
</dependencies>

Why version management is a security decision

The convenience is also the risk. Because the BOM chooses versions for libraries you never explicitly declared, your exposure to a vulnerability in a transitive dependency is decided by the Spring Boot version you pin at the top of the file. Bump the parent version and dozens of transitive versions move at once. Forget to bump it and you silently accumulate known-vulnerable versions.

This is precisely how so many teams stayed exposed to serialization flaws in jackson-databind for months: they had no direct dependency on it, assumed "Spring handles that," and never upgraded the parent. The BOM was doing exactly what it promised, pinning a compatible version, but "compatible" is not the same as "patched." For the specific attack surface, our Jackson databind security guide breaks down the deserialization gadget chains.

Importing the BOM without the parent

If you cannot use spring-boot-starter-parent (for example, you already inherit from a corporate parent POM), import the BOM in dependencyManagement with scope: import instead:

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

This gives you the same managed versions without the parent's plugin configuration and Java-version defaults. In Gradle, the Spring Boot plugin applies the equivalent dependency management automatically.

Overriding a managed version safely

Sometimes a CVE is patched in a point release before Spring picks it up in a new BOM. You need to override a single managed version without disturbing the rest. With spring-boot-starter-parent, the supported way is to redefine the version property the BOM uses:

<properties>
  <jackson-bom.version>2.17.2</jackson-bom.version>
</properties>

Each managed dependency has a corresponding property (jackson-bom.version, tomcat.version, logback.version, and so on) documented in the Spring Boot dependency-versions appendix. Overriding the property is preferable to redeclaring the dependency with an explicit <version>, because the property approach keeps related artifacts in the same family aligned.

A word of caution: overriding a managed version means you have stepped outside the set Spring tested together. Do it deliberately, document why, and remove the override once the mainline BOM catches up. Every override is a small compatibility bet.

The end-of-life trap

The most dangerous BOM problem is not a single stale library. It is running an entire framework line that no longer receives security patches. Spring Boot 2.7 reached open-source end of life on June 30, 2023; a courtesy final patch (2.7.18) shipped in November 2023 to ease migration, and nothing has been patched in the 2.x line since. The current lines have moved to Spring Framework 7 and require Java 17 as a baseline.

If your parent POM still reads 2.7.x, no amount of per-library overriding fixes the fact that the BOM you depend on is frozen. New CVEs in Tomcat, Spring Security, and the framework core simply will not be reflected. The only real remediation is migrating the major version, which for the jump from 2.x to 3.x also means the javax to jakarta namespace change. Spring publishes a dedicated migration guide for exactly this.

An SCA tool such as Safeguard can flag an end-of-life Spring Boot BOM as a maintenance-status finding, not just report the individual CVEs, so the "upgrade the whole line" signal is not lost in a wall of per-package alerts.

Practical workflow

A defensible process around the Spring Boot BOM looks like this:

  • Pin an exact parent version, never a range, and treat bumping it as a reviewed change.
  • Generate an SBOM after every build so you have a record of the versions the BOM actually resolved, not the ones you think it did.
  • Alert on end-of-life framework lines, not only on individual CVEs.
  • Use property overrides for urgent single-library patches, and track them so they get removed on the next BOM upgrade.
  • Automate parent-version bumps with Dependabot or Renovate so you are never more than one review away from current.

The BOM is a powerful centralization of trust. That is exactly why it deserves security attention proportional to its reach.

FAQ

What is the difference between the Spring Boot BOM and the starter parent?

spring-boot-dependencies is the BOM: it only declares managed versions. spring-boot-starter-parent is a parent POM that imports that BOM and also adds plugin configuration, resource filtering, and Java-version defaults. If you already have a corporate parent, import the BOM directly with scope: import instead.

How do I override a single dependency version managed by the BOM?

Redefine the version property the BOM uses, for example <jackson-bom.version>2.17.2</jackson-bom.version> in your properties block. This keeps related artifacts aligned better than redeclaring the dependency with an explicit version. Document the override and remove it once the mainline BOM catches up.

Does the Spring Boot BOM protect me from CVEs automatically?

No. It pins compatible versions, but compatible is not the same as patched. Your CVE exposure is decided by the Spring Boot version at the top of your build. If you never bump it, you accumulate known-vulnerable transitive versions even though the BOM is doing its job.

Is Spring Boot 2.7 still safe to use?

No. Spring Boot 2.7 reached open-source end of life on June 30, 2023, with a final courtesy patch (2.7.18) in November 2023. It receives no further security fixes, so new CVEs in its managed dependencies go unpatched. Migrating to a supported major line is the only real fix.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.