spring-projects / spring-projects/spring-boot
Provide a built-in Actuator drain endpoint for Kubernetes preStop hook polling
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 81.5k
- Forks
- 42.7k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 65
Description
Summary
Spring Boot 2.3 introduced server.shutdown: graceful, liveness/readiness probe
endpoints, and SmartLifecycle-based shutdown ordering. These cover HTTP-level draining
well. However, there is no standard Actuator endpoint that allows a Kubernetes preStop
hook to poll and determine when all application-level in-flight work (async tasks,
Kafka consumer processing, custom thread pools, etc.) has completed before SIGTERM is sent
to the JVM.
Current Behaviour
Spring Boot provides:
/health/liveness— signals whether the application context is alive/health/readiness— signals whether the pod should receive traffic; Spring
automatically setsReadinessState.REFUSING_TRAFFICon shutdown, which causes this to
return 503server.shutdown: graceful— Tomcat waits for in-flight HTTP requests to completeSmartLifecycle— allows beans to participate in ordered shutdown
None of these can be used as a simple HTTP 200/503 polling endpoint for a Kubernetes
preStop hook to determine when async, non-HTTP work is finished.
The Gap
The Kubernetes pod termination sequence is:
- Pod removed from Service endpoints
- preStop hook executes: Spring is fully alive here; no shutdown has started
- SIGTERM sent to JVM process
- Spring shutdown begins (SmartLifecycle, Tomcat drain, etc.)
- SIGKILL after terminationGracePeriodSeconds
The preStop hook runs before SIGTERM reaches the JVM. Spring's own graceful shutdown
therefore cannot participate in this phase. Teams are left implementing their own drain
endpoint, typically:
- A custom
@RestControllerreturning200 OKwhen idle,503while busy - A custom
SmartLifecyclebean that polls registered "drainable" components - Wiring of
ThreadPoolTaskExecutor.getActiveCount()and similar metrics by hand
This is a common enough pattern that every team deploying Spring Boot on Kubernetes
implements it independently.
Proposed Solution
Introduce a Drainable SPI (or reuse SmartLifecycle) and a built-in Actuator
endpoint — e.g. /actuator/drain or a new drain health group — that:
- Aggregates the state of all registered
Drainablebeans (orSmartLifecyclebeans
that have not yet completed theirstop()) - Returns
200 OKwhen all components report no in-flight work - Returns
503 Service Unavailablewith a JSON body listing the still-busy component
names while work is in progress
The endpoint should be exposed under the management port and require no authentication by
default (consistent with /health/liveness and /health/readiness), so that a minimal
preStop script can use it:
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- until wget -qO- http://localhost:8080/actuator/drain; do sleep 2; done
/**
* Register a bean of this type to participate in the built-in drain endpoint.
* Spring Boot will expose aggregate drain state at /actuator/drain.
*/
public interface Drainable {
/** Returns true when this component has no more in-flight work. */
boolean isDrained();
/** Human-readable name used in the /actuator/drain response body. */
String name();
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading Spring Boot Actuator endpoint conventions, SmartLifecycle shutdown ordering, and the Kubernetes preStop sequence described in the issue. Define whether the implementation uses a new Drainable SPI or SmartLifecycle, then verify that the endpoint aggregates registered components, reports busy names with 503, and returns 200 only when all work is drained.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kubernetes, spring-boot
- Domain
- api, backend, devops
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100