Graceful Shutdown — proper termination of a Spring Boot application: finishing current requests, releasing resources, closing connections.
Simple analogy: Graceful Shutdown is like politely leaving a restaurant: you wait for the waiter to bring the bill (finish the current request), say goodbye (release the table), and leave. Instead of "throw money on the table and run out." In production, an abrupt shutdown means lost transactions, corrupted data, and confused users who suddenly get 502 errors.
Why it matters in production: In a Kubernetes cluster with rolling deployments, when a pod is terminated, traffic is still being routed to it. Without graceful shutdown, in-flight requests are dropped mid-execution, database transactions may be left open, and downstream services may receive incomplete data. Graceful shutdown ensures every in-flight request completes before the process exits.
Enabling:
1# application.yml2server:3 shutdown: graceful4spring:5 lifecycle:6 timeout-per-shutdown-phase: 30s
What happens on SIGTERM:
Custom shutdown logic:
1@Component2@Slf4j3public class ShutdownHandler {4 private final ExecutorService executorService;5 private final NotificationService notificationService;6 private final KafkaTemplate<String, String> kafkaTemplate;78 @PreDestroy9 public void onShutdown() {10 log.info("Starting graceful shutdown...");1112 // 1. Stop accepting new work13 executorService.shutdown();1415 // 2. Wait for in-flight tasks16 try {17 if (!executorService.awaitTermination(15, TimeUnit.SECONDS)) {18 executorService.shutdownNow();19 log.warn("Forced shutdown of executor after timeout");20 }21 } catch (InterruptedException e) {22 executorService.shutdownNow();23 Thread.currentThread().interrupt();24 }2526 // 3. Deregister from service discovery27 // discoveryClient.deregister();2829 // 4. Send shutdown notification30 notificationService.notifyShutdown();3132 log.info("Graceful shutdown complete");33 }34}
Kubernetes configuration:
1spec:2 terminationGracePeriodSeconds: 60 # Time for graceful shutdown3 containers:4 - name: app5 lifecycle:6 preStop:7 exec:8 command: ["/bin/sh", "-c", "sleep 5"] # Give time to disconnect from Load Balancer
Production pitfalls:
timeout-per-shutdown-phase higher than your slowest request.preStop sleep — Kubernetes sends SIGTERM while the load balancer still routes traffic to the pod. The 5-second delay gives the LB time to deregister.close() to return connections. Spring Boot handles this automatically, but custom pools may not.Monitoring:
@PreDestroy method with timestamps to trace shutdown duration.server.shutdown=immediate in development for fast restarts, graceful in production.spring.lifecycle.timeout-per-shutdown-phase with Prometheus to detect slow shutdowns.Without graceful shutdown — current requests are interrupted, transactions are not committed, users see errors, and downstream services may be left in inconsistent states.