Spring Framework 7 removed a dependency from most of my projects. It also deliberately did not remove the one everyone assumes it did.
## Retry is core now
spring-retry as a separate dependency is no longer necessary for the common case.
java
@Retryable(includes = MessageDeliveryException.class,
maxRetries = 4, delay = 100,
jitter = 10, multiplier = 2, maxDelay = 1000)
public void sendNotification() { ... }
Defaults are 3 retries and a one-second delay. Be precise about the arithmetic, because this is the sort of thing people get wrong in an incident review:
total attempts = 1 initial + maxRetries
maxRetries = 4 means at most five invocations of a downstream that may already be struggling. Enable it with @EnableResilientMethods on a @Configuration class. That is the whole setup.
## The underrated annotation
java
@ConcurrencyLimit(10)
public void sendNotification() { ... }
It caps how many threads can be inside a method at once, blocking the rest until a slot frees up.
The reason this landed *now* is virtual threads.
Your thread pool size used to be an implicit concurrency limit. A 20-thread pool could not hit a downstream with 500 concurrent calls, because it could not produce 500 threads. That protection was never designed; it was a side effect of the thing that was also your bottleneck.
Move to virtual threads and the accidental protection disappears with the bottleneck. No pool, no limit. Your fragile legacy SOAP endpoint now receives everything you can generate, all at once — and you removed its shield in the same commit where you celebrated removing your own.
## There is no @CircuitBreaker in core, and that is correct
Retry and concurrency limiting are local decisions. One method, one call, no shared state. Nothing to configure beyond numbers, nothing to argue about.
A circuit breaker is stateful. It tracks failure rates over a window and forms an opinion about whether a downstream is healthy. That needs a config surface, metrics, half-open probing, and — the hard part — a definition of "unhealthy" that someone has to defend.
Spring put the two primitives with no policy debate into the framework and left the opinionated one to Spring Cloud Circuit Breaker and Resilience4j. A half-implemented circuit breaker is worse than a well-understood external one, because it fails in a way nobody has documented.
## What to do with this
Drop spring-retry. Keep Resilience4j if you circuit-break. And go and look at what your concurrency limits actually are now that you are on virtual threads.
If you have migrated: what is currently stopping your service from opening ten thousand concurrent connections to a downstream that can handle fifty?