Spring 7.0.4 Unveiled: 40+ New Features, 15 Fixes, and the End of the Classic Deadlock
A real‑world K8s pod hang caused by a race between Spring's shutdown paths triggered a classic deadlock in versions 7.0.0‑7.0.3, which Spring 7.0.4 resolves along with 40+ new features, performance boosts, and dozens of other bug fixes, offering concrete upgrade guidance for developers.
Scenario
At 3 AM an alert fired: a core service pod in a Kubernetes cluster repeatedly failed its health check, was killed by kubelet, and a new pod started but hung. The log stopped at Initializing Spring DispatcherServlet. CPU usage was near 0 % and jstack showed two threads waiting on each other – AbstractApplicationContext.close() and the JVM ShutdownHook, forming a classic deadlock.
This issue was observed in the wild on Spring 7.0.0 – 7.0.3. Spring 7.0.4, released in February 2026, pins the bug.
Root Cause
During application shutdown Spring runs two parallel paths:
Normal shutdown via ConfigurableApplicationContext.close(), which publishes ContextClosedEvent and destroys beans.
The JVM ShutdownHook triggered by System.exit.
In Kubernetes the following sequence can make the two paths overlap: SIGTERM arrives → graceful shutdown starts.
The application begins its close() process.
If graceful shutdown times out, kubelet sends SIGKILL.
Before SIGKILL, the JVM may also fire its ShutdownHook.
Both paths compete for the same lock, causing a deadlock.
The bug is probabilistic, depending on GC pauses, thread scheduling, and bean destruction time; it rarely appears in local mvn test runs but surfaces intermittently in production.
Fix in Spring 7.0.4
The ConfigurableApplicationContext state machine was rewritten with an independent shutdown flag and CAS operation, guaranteeing that close() and ShutdownHook never enter the destroy phase simultaneously. Whichever path arrives first proceeds, and the later one detects that “the other side is already shutting down” and yields.
Self‑Check Checklist
Pod starts but never exits, process stays alive. jstack shows close() and ShutdownHook threads waiting on each other.
Issue reproduces only in container/K8s environments.
Higher deployment frequency increases occurrence.
Other Notable Fixes in 7.0.4
#36293 – ConcurrentReferenceHashMap lock‑contention bug that silently degrades throughput under high concurrency.
#36298 – Header modifications in HandlerInterceptor were not propagated to downstream services.
#36266 – WebSocket StompBrokerRelayMessageHandler failed to reconnect after broker restart.
#36285 + #36226 – Message converters now support MIME wildcard */*; HeadersAdapter.remove() returns null instead of an empty list.
Performance Boosts (30 s → 15 s)
Spring 7.0.4 optimizes three dimensions, whose effects multiply rather than add. Community benchmarks report 30‑50 % faster startup and 10‑20 % faster request handling, varying with JDK version and application size.
Dimension 1: Request‑Mapping Routing
Hash algorithm replaced with a faster implementation (+5 % at million QPS).
Bean lookup path shortened by removing redundant reflection objects.
Single‑URL @RequestMapping patterns bypass generic pattern matching.
Version‑mapping decoupled, eliminating extra performance tax for API‑versioned projects.
Community tests on spring-petclinic show ~15‑20 % latency reduction for gateway‑style services.
Dimension 2: Annotation Parsing
All annotation‑driven features ( @Transactional, @Cacheable, @Valid, etc.) previously re‑parsed on each call. Spring 7.0.4 caches the first parse result, so subsequent invocations hit memory directly.
Dimension 3: Validation Reflection Slimming
Bean Validation now reduces reflective Class.getAnnotations() calls when determining validation groups, cutting overhead in bulk‑import or multi‑step form scenarios.
Combined, startup improves 30‑50 % and runtime request handling 10‑20 % (measured with spring-petclinic).
Upgrade Decision Summary
Spring 7.x + K8s : Upgrade today; risk near‑zero because deadlock fix and startup boost are essential.
Spring 7.x + traditional deployment : Upgrade in next iteration; zero risk, no breaking changes.
Spring Boot 4.0 : Wait for 4.0.4, then upgrade; Boot will bundle 7.0.4 automatically.
Spring 6.x / Boot 3.x : Do not rush; migration to Jakarta namespaces is costly.
Hard Preconditions for Moving from 6.x
JDK ≥ 17 (prefer 21+).
All dependencies migrated from javax.* to jakarta.*.
Read the official migration guide before changing pom files.
JDK Version Impact
JDK 17 : ~10‑15 % startup speed‑up; only routing and annotation cache gains, no virtual‑thread benefits.
JDK 21 : ~25‑35 % startup speed‑up; virtual threads available but scheduler not fully optimized.
JDK 25 : ~40‑50 % startup speed‑up; Reactor 2025.0.3 + Spring 7.0 fully exploit virtual‑thread async startup.
Official release notes: https://github.com/spring-projects/spring-framework/releases/tag/v7.0.4
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.
