If your Spring Boot application lets the Boot BOM pick its library versions, which is the default with the Boot parent POM or Gradle plugin, your security fix arrives on Spring Boot's schedule, not the library's. We measured that schedule. When a fix ships in the same minor line Boot already manages, a later Boot patch release takes it, in our data a median of 23 days after the fixed version reached Maven Central. When the fix only ships in a newer minor, a Boot patch release never takes it, by design. That second case is the one teams miss.
This post is for Java teams on Spring Boot, and for anyone who relies on a BOM or platform to manage dependency versions. It covers the rule that decides what Boot upgrades, what the data shows across 56 Boot releases, what the latest releases manage today, and a script that tells you which version properties to override.
The short answer
Spring Boot patch releases only upgrade managed third-party libraries at the patch level. Minor upgrades wait for a Boot minor release. Across Boot 3.3 to 4.1 and the Maven advisories GitHub reviewed from June 2025 to October 2026, a later Boot patch release picked up the fix in 264 cases, a median of 23 days after the fixed version appeared on Maven Central. In 80 cases, a Boot line kept shipping an affected version after the fix existed, and in 70 of those the fix was only available in a newer minor of the library. As of 19 October 2026, Boot 4.0.8 manages versions with 51 known advisories and 4.1.1 with 32. For anything fixed only in a newer minor, you have to override the version property yourself.
The rule that decides what Boot upgrades
Spring Boot's own Supported Versions page states the policy plainly: managed libraries are "upgraded only at the patch level for any given Spring Boot patch release". Minor and major upgrades of a library happen only in Boot minor or major releases.
That's a sensible rule. A patch release should be safe to take without reading release notes, and a library's minor version can change behaviour. The consequence is mechanical, though. If Boot 4.0.x manages ActiveMQ 6.1.8, and a vulnerability is fixed in 6.2.4 with no 6.1.x fix, no 4.0.x release will ever move to it. Teams that wait for Boot to fix it wait until they move to the next Boot minor line, which might bring a newer ActiveMQ, or might not.
What the data shows
We took every general-availability release of spring-boot-dependencies from 3.3.0 (May 2024) to 4.1.1 (August 2026), 56 releases across five minor lines, and resolved every managed third-party artifact, including those that come through imported BOMs such as Netty's and Jetty's. We left out Spring's own projects, which release together with Boot. Then we matched each managed version against the GitHub-reviewed Maven advisories in OSV published between 1 June 2025 and 16 October 2026 that name a fixed version.
The unit is an advisory in a Boot line: one advisory affecting Boot 3.5.x and 4.0.x counts twice. Of 683 such pairs, we set aside 321 where the line's last release came before the fix existed, and 18 where the fixed version isn't on Maven Central. That leaves 344:
| Outcome | Pairs | Advisories |
|---|---|---|
| A later Boot patch release moved to a fixed version | 264 | 139 |
| The line's latest release came after the fix, and still manages an affected version | 80 | 44 |
| ... because the fix exists only in a newer minor of the library | 70 | |
| ... because the fix came out after the line's latest release was cut | 10 |
For the 264 that were fixed:
| Measure | Result |
|---|---|
| Median time from fixed version on Maven Central to the Boot release that manages it | 23 days |
| Within 31 days | 158 (60%) |
| 75th percentile | 40 days |
| Longest | 69 days |
| Median by GitHub severity | critical 16 days, high 22, moderate 29, low 15 |
| Libraries involved most often | Netty, Tomcat and Jetty, then Logback, Log4j and the MariaDB driver |
We measured from the date the fixed version's POM appeared on Maven Central. For Apache projects that can be several days before the release is announced: Tomcat 11.0.25 reached Central on 12 August, and Tomcat's security page dates the release 18 August. So the real gap between announcement and Boot release is often shorter than these numbers.
The 70 newer-minor cases cluster in a few libraries: the ActiveMQ client and broker (managed at 6.1.8, many fixes only in 6.2.x), the RabbitMQ Java client (5.27.1 and 5.30.0, fixes in 5.31 to 5.36), Hazelcast (5.5.0, fixes in 5.7.0), Apache HttpComponents (core 5.3.6, fixes in 5.4.3), Log4j on the 3.5 line (2.24.3, fixes in 2.25.x) and OpenTelemetry. Many of these are optional integrations. If you don't use ActiveMQ, its managed version doesn't matter to you. If you do, it matters a lot.
What the latest releases manage today
The newest Boot releases are 4.0.8 and 4.1.1, both from 20 August 2026, and 3.5.16 from 25 June. No general-availability Boot release has come out since (only the 4.2.0-M2 milestone, on 24 September), so anything fixed after 20 August is waiting on the next one. Running the script below for 4.0.8 on 19 October:
activemq.version = 6.1.8: 12 advisories
1 fixed on 6.1.x, so a later Boot patch release can pick them up
11 fixed only in another minor (6.2.4, 6.2.5, 6.2.6): override activemq.version yourself
artemis.version = 2.43.0: 2 advisories
1 fixed only in another minor (2.53.0): override artemis.version yourself
1 with no fixed version recorded
derby.version = 10.16.1.1: 1 advisory
1 fixed on 10.16.x, so a later Boot patch release can pick them up
freemarker.version = 2.3.34: 1 advisory
1 fixed on 2.3.x, so a later Boot patch release can pick them up
hazelcast.version = 5.5.0: 2 advisories
2 fixed only in another minor (5.7.0): override hazelcast.version yourself
httpclient5.version = 5.5.2: 1 advisory
1 fixed only in another minor (5.6.3): override httpclient5.version yourself
httpcore5.version = 5.3.6: 2 advisories
2 fixed only in another minor (5.4.3, 5.5-beta2): override httpcore5.version yourself
jackson-2-bom.version = 2.21.5: 7 advisories
7 fixed on 2.21.x, so a later Boot patch release can pick them up
jackson-bom.version = 3.1.5: 7 advisories
7 fixed on 3.1.x, so a later Boot patch release can pick them up
opentelemetry.version = 1.55.0: 1 advisory
1 fixed only in another minor (1.62.0): override opentelemetry.version yourself
r2dbc-mariadb.version = 1.3.1: 2 advisories
2 fixed only in another minor (1.4.1): override r2dbc-mariadb.version yourself
rabbit-amqp-client.version = 5.27.1: 10 advisories
10 fixed only in another minor (5.35.0, 5.36.0, 5.36.1): override rabbit-amqp-client.version yourself
tomcat.version = 11.0.24: 3 advisories
3 fixed on 11.0.x, so a later Boot patch release can pick them up
51 advisories across 1264 managed third-party artifacts in Spring Boot 4.0.8
Two things stand out. Tomcat 11.0.24 is affected by three advisories GitHub rates critical, fixed in 11.0.25 on the same minor line, so a future 4.0.x or 4.1.x release can take it. Tomcat's own security page rates at least one of them, CVE-2026-68525, as low, so read the project's assessment before you panic. And Tomcat 11.0.26, released in September, fixes more. The two Jackson lines have seven advisories each, fixed in 2.21.6 or 2.21.7 and in 3.1.6 or 3.1.7, so they're also fixable on their current minor.
Everything in the "another minor" lines needs action from you. 4.1.1 is in better shape, with 32 advisories: it has already moved to newer ActiveMQ, HttpComponents and OpenTelemetry versions, but still manages the same Tomcat, Jackson, Hazelcast and RabbitMQ client versions as 4.0.8 or close to them.
Check your Boot version
This script takes a Spring Boot version, downloads its BOM and the BOMs it imports from Maven Central, asks OSV which managed third-party versions have GitHub advisories, and groups the results by the version property you'd override. It uses only the Python standard library and exits with status 1 if anything is affected:
#!/usr/bin/env python3
"""For a Spring Boot version, list the third-party libraries its BOM manages
at a version with a known vulnerability, the fixed version, and whether a
Boot patch release can pick it up (same minor) or you must override the
version property yourself (fix only in a newer minor).
Usage: python3 boot_bom_check.py 4.0.8"""
import json
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
NS = "{http://maven.apache.org/POM/4.0.0}"
CENTRAL = "https://repo1.maven.org/maven2"
UA = {"User-Agent": "boot-bom-check/1.0"}
def fetch(url, data=None):
req = urllib.request.Request(url, data=data, headers=UA)
with urllib.request.urlopen(req, timeout=60) as r:
return r.read()
def pom(group, artifact, version):
path = f"{CENTRAL}/{group.replace('.', '/')}/{artifact}/{version}/{artifact}-{version}.pom"
return ET.fromstring(fetch(path))
def managed(root, version):
"""Yield (group:artifact, version, property, is_import) from a BOM."""
props = {p.tag.replace(NS, ""): (p.text or "").strip()
for p in (root.find(NS + "properties") if root.find(NS + "properties") is not None else [])}
props["project.version"] = version
def resolve(text):
for _ in range(5):
text = re.sub(r"\$\{([^}]+)\}", lambda m: props.get(m.group(1), m.group(0)), text)
return text
deps = root.find(f"{NS}dependencyManagement/{NS}dependencies")
for d in deps if deps is not None else []:
raw = d.findtext(NS + "version") or ""
prop = re.fullmatch(r"\$\{([^}]+)\}", raw.strip())
ga = resolve(d.findtext(NS + "groupId")) + ":" + resolve(d.findtext(NS + "artifactId"))
yield ga, resolve(raw), prop.group(1) if prop else None, d.findtext(NS + "scope") == "import"
def minor(v):
return ".".join(re.findall(r"\d+", v)[:2])
def main(boot):
libs, bom_version = {}, {}
for ga, ver, prop, is_import in managed(pom("org.springframework.boot", "spring-boot-dependencies", boot), boot):
if not is_import:
libs[ga] = (ver, prop)
continue
bom_version[prop] = ver
g, a = ga.split(":")
try:
inner = pom(g, a, ver)
except Exception:
continue
for iga, iver, _, _ in managed(inner, ver):
libs.setdefault(iga, (iver, prop))
libs = {ga: v for ga, v in libs.items()
if not ga.startswith("org.springframework") and "${" not in v[0]}
names = sorted(libs)
results = []
for i in range(0, len(names), 500):
chunk = names[i:i + 500]
body = json.dumps({"queries": [{"package": {"ecosystem": "Maven", "name": n},
"version": libs[n][0]} for n in chunk]}).encode()
results += list(zip(chunk, json.loads(fetch("https://api.osv.dev/v1/querybatch", body))["results"]))
groups = {}
for name, res in results:
ver, prop = libs[name]
g = groups.setdefault(prop or name, {"ver": bom_version.get(prop, ver), "same": set(), "newer": {}, "nofix": set()})
for v in res.get("vulns", []):
rec = json.loads(fetch(f"https://api.osv.dev/v1/vulns/{v['id']}"))
if not rec["id"].startswith("GHSA"):
continue
cve = next((x for x in rec.get("aliases", []) if x.startswith("CVE")), rec["id"])
fixes = sorted({e["fixed"] for a in rec.get("affected", []) if a["package"]["name"] == name
for r in a.get("ranges", []) for e in r["events"] if "fixed" in e})
if any(minor(f) == minor(ver) for f in fixes):
g["same"].add(cve)
elif fixes:
g["newer"][cve] = fixes
else:
g["nofix"].add(cve)
found = 0
for prop, g in sorted(groups.items()):
newer = {c: f for c, f in g["newer"].items() if c not in g["same"]}
n = len(g["same"] | set(newer) | g["nofix"])
if not n:
continue
found += n
print(f"{prop} = {g['ver']}: {n} {'advisory' if n == 1 else 'advisories'}")
if g["same"]:
print(f" {len(g['same'])} fixed on {minor(g['ver'])}.x, so a later Boot patch release can pick them up")
if newer:
lines = sorted({x for f in newer.values() for x in f if minor(x) != minor(g['ver'])},
key=lambda x: [int(p) for p in re.findall(r"\d+", x)])
print(f" {len(newer)} fixed only in another minor ({', '.join(lines[-3:])}): override {prop} yourself")
if g["nofix"]:
print(f" {len(g['nofix'])} with no fixed version recorded")
print(f"{found} advisories across {len(libs)} managed third-party artifacts in Spring Boot {boot}")
return 1 if found else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "4.0.8"))
It checks what the BOM manages, not what your application uses. A managed version only matters if your build pulls that artifact in, so cross-check with mvn dependency:tree or gradle dependencies before you change anything.
How to override a managed version
With spring-boot-starter-parent in Maven, set the property:
<properties>
<tomcat.version>11.0.26</tomcat.version>
<activemq.version>6.2.6</activemq.version>
</properties>
With the Spring dependency-management plugin in Gradle, the same properties work through ext:
ext['tomcat.version'] = '11.0.26'
ext['activemq.version'] = '6.2.6'
If you import spring-boot-dependencies as a BOM instead of using the parent, properties don't apply. Declare the artifact, or the library's own BOM, in your dependencyManagement before the Spring Boot import, because the first declaration wins. For a minor-version jump like ActiveMQ 6.1 to 6.2, run your integration tests: that's exactly the risk Boot's policy avoids in patch releases.
Once you've overridden versions, you own them. Here's Safeguard's interactive walkthrough of seeing which findings already have a fix available:
What to do
- Run the script on your Boot version, then check which of the flagged artifacts your build actually uses.
- Override "another minor" fixes yourself. A Boot patch release won't do it, and the next Boot minor may not either.
- For same-minor fixes, decide whether to wait. A 23-day median is fine for a moderate bug in an optional module. For an exploitable bug in Tomcat or Netty, override now and drop the override when Boot catches up.
- Track your overrides. Keep a list with the reason for each, and remove them when a Boot upgrade manages an equal or newer version. A forgotten override pins you to an old version later.
- Plan Boot minor upgrades on a schedule, because that's the only way the BOM itself moves you to newer library minors. Our guide on remediating transitive dependency vulnerabilities covers the general techniques.
The fair view
Boot's policy protects you from exactly the kind of surprise a minor upgrade can bring, and a median of 23 days for same-minor fixes is reasonable for a BOM that manages over a thousand artifacts. We don't know why no general-availability Boot release has shipped since 20 August (milestone 4.2.0-M2 came out on 24 September), and the numbers don't say. Many of the slow cases are optional integrations that most applications never load.
But the BOM can't know what you run. It manages versions for everything anyone might use, on a cadence that suits everyone, and it deliberately won't jump a minor in a patch release. That makes "we're on the latest Boot patch" an incomplete security answer. The complete one is the latest Boot patch, plus the overrides for the libraries you actually depend on, plus a date to remove each one.
Check yourself
A vulnerability in a managed library is fixed only in a newer minor version. Will the next Spring Boot patch release pick it up?
No. Spring Boot patch releases upgrade managed libraries only at the patch level. You need to override the version property yourself, or wait for a Boot minor release that manages the newer minor.
You import spring-boot-dependencies as a BOM and set tomcat.version in your properties. Does Tomcat change?
No. Version properties only work with the Spring Boot parent POM or the Gradle dependency-management plugin. With a BOM import, declare the Tomcat artifacts (or Tomcat's BOM) in your own dependencyManagement before the Spring Boot import.
The checker flags ActiveMQ, but your app doesn't use JMS. Should you override activemq.version?
Not for that reason. The BOM manages versions for artifacts you might use, and a managed version only reaches your application if your build pulls the artifact in. Confirm with mvn dependency:tree or gradle dependencies first.