The full picture
# application.yml
server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 25s
# k8s deployment
spec:
terminationGracePeriodSeconds: 40 # > preStop + drain
containers:
- lifecycle:
preStop:
exec: { command: ["sh", "-c", "sleep 8"] } # let LB/endpoints catch up
# SIGTERM timeline:
# t0 preStop sleep (still serving, endpoints removing pod)
# t8 SIGTERM -> readiness DOWN, server stops accepting, drains in-flight
# Kafka/scheduler stop (SmartLifecycle), pools close, @PreDestroy
# t<40 clean exit — else SIGKILL (the thing all of this prevents)- Order dependencies via SmartLifecycle phases: consumers/listeners stop in high phases first; DataSource is a low-phase close — reversing it means in-flight work dies on 'pool is closed'.
- In-flight beyond HTTP:
@Scheduledjobs mid-run,@Asyncqueues, Kafka batches — each needs a stop-and-drain story; executor beans getsetWaitForTasksToCompleteOnShutdown(true)+ a timeout. - The SIGKILL boundary: after grace expires nothing runs — never park durability (flush that 'must' happen) in shutdown hooks; crash-safety comes from transactions/idempotency, shutdown is just politeness at scale.
- Test it: a load test that rolls pods mid-traffic and asserts zero 5xx is the only proof; teams discover their shutdown story during their first busy-hour deploy otherwise.