Graceful shutdown and lifecycle ordering

Spring Boot · lesson 35 of 39 · 5 min read

Stop taking new work, finish what is in flight, then release resources - in that order.

Open this lesson in the learning hub

Key points

  • A container sends SIGTERM and starts a countdown. Without graceful shutdown, the JVM exits immediately and every in-flight request becomes a client-side error.
  • server.shutdown=graceful makes the connector stop accepting new connections while existing requests finish, bounded by spring.lifecycle.timeout-per-shutdown-phase.
  • There is a subtlety that graceful shutdown alone does not solve: the load balancer may still be routing to this pod when SIGTERM arrives. Failing readiness first, then pausing, is what drains traffic cleanly.
  • Shutdown runs SmartLifecycle beans in reverse phase order, so something started early is stopped last. That is how a message consumer stops before the datasource it writes through closes.
  • @PreDestroy runs during context close, after lifecycle stop. It is the right place to flush a buffer, and the wrong place to make a slow network call.
  • Kubernetes kills the pod when its termination grace period expires, so the shutdown timeout must be shorter than that value or the process is killed mid-request anyway.

Example

server:
  shutdown: graceful           # stop accepting, let in-flight requests finish

spring:
  lifecycle:
    timeout-per-shutdown-phase: 25s   # must be < the pod terminationGracePeriodSeconds

management:
  endpoint:
    health:
      probes:
        enabled: true        # /actuator/health/liveness and /readiness

---
# The consumer stops before anything it depends on. Reverse phase order on stop
# is what guarantees that, so no phase juggling is needed at the call site.
#
#   start:  phase -100 (warmup)  ->  phase 0 (consumer)  ->  MAX (web server)
#   stop:   MAX (web server)     ->  phase 0 (consumer)  ->  phase -100
#
# Kubernetes side - drain BEFORE the process is asked to stop:
#
#   lifecycle:
#     preStop:
#       exec:
#         command: ["sh", "-c", "sleep 5"]   # let endpoints propagate first
#   terminationGracePeriodSeconds: 40        # > the 25s above

Fail readiness, wait for the load balancer to notice, then let in-flight work finish inside a timeout shorter than the platform grace period.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.