Why Spring 7.0.4’s Hidden Bugs and Performance Boosts Matter to Your Apps
The article walks through a real‑world deadlock bug in Spring 7.0.0‑7.0.3, explains how Spring 7.0.4 fixes it, highlights additional hidden issues, details three major performance optimizations, shows the impact of different JDK versions, and provides a concise upgrade decision guide.
One Real‑World Scenario
At 3 AM an alert fired: a pod in a Kubernetes cluster repeatedly failed its health check, was killed by the kubelet, and a new pod started but got stuck. The logs stopped at Initializing Spring DispatcherServlet, CPU usage was near zero, and a jstack dump revealed a deadlock between AbstractApplicationContext.close() and the JVM ShutdownHook.
Restarting the pod temporarily fixed the issue, but it reappeared later. This is not a hypothetical case – it happened on Spring 7.0.0 ~ 7.0.3. Spring 7.0.4, released in February 2026, permanently fixed the bug.
Root Cause of the Deadlock (Issue #36260)
Spring has two parallel shutdown paths:
Normal ConfigurableApplicationContext.close() which publishes ContextClosedEvent and destroys beans.
The JVM ShutdownHook triggered by System.exit.
In Kubernetes, a graceful shutdown (SIGTERM) starts close(). If the shutdown times out, kubelet sends SIGKILL, but the JVM may fire its own ShutdownHook before SIGKILL. Both paths compete for the same lock, causing a classic deadlock. The bug is probabilistic, depending on GC pauses, thread scheduling, and bean destruction time, making it hard to reproduce locally.
How Spring Fixed It
The team rewrote the shutdown state machine in ConfigurableApplicationContext, adding an independent shutdown flag and CAS operations so that close() and the ShutdownHook never run the destruction logic simultaneously. In plain terms: whichever path arrives first proceeds, and the later one simply yields because “someone else is already closing”.
Self‑Check for the Deadlock
Pod starts but never exits. jstack shows close() and ShutdownHook threads waiting on each other.
Issue only reproduces in K8s/container environments.
Higher deployment frequency increases occurrence.
Other Notable Fixes in 7.0.4
#36293 – ConcurrentReferenceHashMap lock contention caused intermittent throughput drops under high concurrency.
#36298 – Headers added in HandlerInterceptor were not propagated to downstream services.
#36266 – StompBrokerRelayMessageHandler failed to reconnect after the broker restarted.
#36285 & #36226 – Message converters now support MIME wildcards ( */*) and HeadersAdapter.remove() returns null instead of an empty list.
"30 seconds to 15 seconds" – Performance Optimizations
Spring 7.0.4 improves performance in three dimensions, and their effects multiply rather than add, yielding 30‑50 % faster startup and 10‑20 % faster request handling for many workloads.
Dimension 1: Faster Request Mapping
Replaced URL‑pattern hash algorithm with a more efficient implementation (+5 % speedup at million QPS).
Shortened bean lookup path by removing redundant intermediate objects during HandlerMethod parsing.
Fast‑path for single‑URL @RequestMapping patterns, bypassing generic pattern matching.
Decoupled API version mapping to avoid extra performance tax.
Community tests show ~15‑20 % latency reduction for gateway‑style services.
Dimension 2: Annotation Parsing Cache
Previously, every method invocation re‑parsed annotations such as @Transactional, @Cacheable, @Valid, and @RequestMapping. Spring 7.0.4 now caches the first parsing result, so subsequent calls hit an in‑memory cache.
@MyTransactional
@MyCacheable
@MyValidated
public void doSomething() { ... }After the change, the combined three‑layer annotation parsing happens only once.
Dimension 3: Validation Reflection Slimming
Bean Validation previously called Class.getAnnotations() for each validation group, which became expensive in bulk‑import or multi‑step form scenarios. Spring 7.0.4 reduces the number of reflective calls on this path.
Combined, these optimizations give a 30‑50 % startup speedup and 10‑20 % runtime request‑handling improvement, verified with the spring-petclinic benchmark.
JDK Version Determines Your Gains
Spring 7.0.4 upgrades Reactor to 2025.0.3, which heavily optimizes virtual‑thread scheduling. The benefits vary by JDK:
JDK 17 – ~10‑15 % startup boost (no virtual‑thread gains).
JDK 21 – ~25‑35 % boost (virtual threads available but not fully optimized).
JDK 25 – ~40‑50 % boost (full virtual‑thread and Reactor optimizations).
Other dependency upgrades include Micrometer 1.6.3, ASM 9.9.1 (JDK 25 bytecode support), and Apache POI 5.5 (large‑file export improvements).
Upgrade Decision Matrix
Spring 7.x + K8s : Upgrade now – deadlock fix and startup boost are critical, risk is near zero.
Spring 7.x + Traditional deployment : Upgrade in the next iteration – no breaking changes.
Spring Boot 4.0 : Wait for 4.0.4, then upgrade – Boot will bundle Spring 7.0.4 automatically.
Still on Spring 6.x / Boot 3.x : Do not rush; first ensure JDK ≥ 17, migrate all dependencies from javax.* to jakarta.*, and read the official migration guide before changing versions.
Hard Prerequisites for Moving from 6.x to 7.x
JDK 17 or newer (21+ recommended).
All dependencies must be migrated to Jakarta namespaces ( javax.servlet → jakarta.servlet, javax.persistence → jakarta.persistence, etc.).
Read and follow the official migration guide at https://spring.io/projects/spring-boot before updating pom files.
One‑Line Takeaway
Spring 7.0.4 may not be flashy, but it silently fixes painful bugs, speeds up startup, and reduces time spent on troubleshooting – delivering far more value to production environments than any headline‑grabbing new API.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
