google-site-verification: google61fe8ba583a51912.html
Graceful Shutdown in Spring Boot: Ensuring Smooth Application Termination

Graceful shutdown is a critical feature for production-ready Spring Boot applications that ensures your application can complete ongoing requests, release resources properly, and maintain data integrity when being stopped.

Understanding Graceful Shutdown

Graceful Shutdown allows an application to:

  • Complete processing of ongoing requests
  • Reject new requests
  • Release database connections, thread pools, and other resources
  • Finish current transactions
  • Save application state if needed

Configuration Methods

Method 1: Application Properties Configuration

Configure graceful shutdown through application.properties or application.yml:

application.properties

# Enable graceful shutdown server.shutdown=graceful # Time to wait for ongoing requests to complete (default: 30s) spring.lifecycle.timeout-per-shutdown-phase=30s # Additional Tomcat specific configuration (if using embedded Tomcat) server.tomcat.connection-timeout=30s server.tomcat.keep-alive-timeout=30s

application.yml

server: shutdown: graceful tomcat: connection-timeout: 30s keep-alive-timeout: 30s spring: lifecycle: timeout-per-shutdown-phase: 30s # For Undertow server server: undertow: threads: worker: 64 io: 4

Method 2: Programmatic Configuration

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GracefulShutdownConfig { @Bean public WebServerFactoryCustomizer<TomcatServletWebServerFactory> gracefulShutdownCustomizer() { return factory -> factory.addConnectorCustomizers(connector -> { connector.setProperty("relaxedQueryChars", "[]"); connector.setProperty("relaxedPathChars", "[]"); }); } }

Implementing Custom Graceful Shutdown

1. Custom Graceful Shutdown Component

import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.Status; import java.util.concurrent.atomic.AtomicBoolean; @Component public class GracefulShutdown implements ApplicationListener<ContextClosedEvent> { private final AtomicBoolean shuttingDown = new AtomicBoolean(false); @Autowired(required = false) private HealthEndpoint healthEndpoint; public boolean isShuttingDown() { return shuttingDown.get(); } @Override public void onApplicationEvent(ContextClosedEvent event) { shuttingDown.set(true); System.out.println("Application shutdown initiated. Starting graceful shutdown..."); // Add custom shutdown logic here performCleanup(); System.out.println("Graceful shutdown completed."); } private void performCleanup() { try { // Wait for ongoing processing to complete Thread.sleep(5000); // Close custom resources closeCustomResources(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("Shutdown was interrupted"); } } private void closeCustomResources() { // Implement resource cleanup logic System.out.println("Closing custom resources..."); } }

2. Spring Boot Actuator for Graceful Shutdown

Add Actuator dependency and configure shutdown endpoint:

pom.xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

application.properties

# Enable shutdown endpoint management.endpoints.web.exposure.include=health,info,shutdown management.endpoint.shutdown.enabled=true # Custom management port (optional) management.server.port=8081

Custom Shutdown Endpoint

import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Map; @Component @Endpoint(id = "graceful-shutdown") public class CustomGracefulShutdownEndpoint { private final GracefulShutdownHandler gracefulShutdownHandler; public CustomGracefulShutdownEndpoint(GracefulShutdownHandler gracefulShutdownHandler) { this.gracefulShutdownHandler = gracefulShutdownHandler; } @WriteOperation public Map<String, String> gracefulShutdown() { new Thread(() -> { try { Thread.sleep(1000); // Give time for response gracefulShutdownHandler.initiateShutdown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); return Collections.singletonMap("message", "Graceful shutdown initiated"); } }

Database Connection Cleanup

1. DataSource Configuration for Graceful Shutdown

import org.springframework.boot.jdbc.DataSourceUnwrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import javax.sql.DataSource; import java.sql.SQLException; @Configuration public class DataSourceGracefulShutdownConfig { @Bean public ApplicationListener<ContextClosedEvent> dataSourceShutdownListener(DataSource dataSource) { return event -> { System.out.println("Closing database connections..."); try { if (DataSourceUnwrapper.isHikari(dataSource)) { DataSourceUnwrapper.getHikariDataSource(dataSource).close(); } // Add other data source types as needed } catch (SQLException e) { System.err.println("Error closing data source: " + e.getMessage()); } }; } }

2. HikariCP Specific Configuration

# HikariCP graceful shutdown configuration spring.datasource.hikari.max-lifetime=600000 spring.datasource.hikari.leak-detection-threshold=60000 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.maximum-pool-size=20

Thread Pool Graceful Shutdown

1. Configuring Async Thread Pool Shutdown

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Bean(name = "taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("AsyncThread-"); executor.setWaitForTasksToCompleteOnShutdown(true); // Important for graceful shutdown executor.setAwaitTerminationSeconds(30); // Wait up to 30 seconds executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } @Override public Executor getAsyncExecutor() { return taskExecutor(); } }

2. Custom Thread Pool Manager

import org.springframework.beans.factory.DisposableBean; import org.springframework.stereotype.Component; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @Component public class CustomThreadPoolManager implements DisposableBean { private final ExecutorService executorService; public CustomThreadPoolManager() { this.executorService = Executors.newFixedThreadPool(10); } public void submitTask(Runnable task) { if (!executorService.isShutdown()) { executorService.submit(task); } } @Override public void destroy() throws Exception { System.out.println("Shutting down custom thread pool..."); executorService.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { // Cancel currently executing tasks executorService.shutdownNow(); // Wait again for tasks to respond to being cancelled if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { System.err.println("Thread pool did not terminate"); } } } catch (InterruptedException ie) { // Cancel if current thread also interrupted executorService.shutdownNow(); Thread.currentThread().interrupt(); } System.out.println("Custom thread pool shutdown completed"); } }

Handling HTTP Requests During Shutdown

1. Request Interceptor for Graceful Shutdown

import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class GracefulShutdownInterceptor implements HandlerInterceptor { private final GracefulShutdown gracefulShutdown; public GracefulShutdownInterceptor(GracefulShutdown gracefulShutdown) { this.gracefulShutdown = gracefulShutdown; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (gracefulShutdown.isShuttingDown()) { response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); response.getWriter().write("Service is shutting down"); return false; } return true; } }

2. Web Configuration

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private GracefulShutdownInterceptor gracefulShutdownInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(gracefulShutdownInterceptor); } }

Complete Graceful Shutdown Handler

import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.CompletableFuture; @Component public class GracefulShutdownHandler implements ApplicationListener<ContextClosedEvent> { private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); @Autowired private CustomThreadPoolManager threadPoolManager; @Autowired(required = false) private DataSource dataSource; public boolean isShutdownInProgress() { return shutdownInProgress.get(); } public void initiateShutdown() { shutdownInProgress.set(true); // Custom shutdown trigger logic } @Override public void onApplicationEvent(ContextClosedEvent event) { shutdownInProgress.set(true); System.out.println("=== Starting Graceful Shutdown ==="); // Phase 1: Stop accepting new requests stopAcceptingNewRequests(); // Phase 2: Wait for ongoing requests to complete waitForOngoingRequests(); // Phase 3: Close resources closeResources(); // Phase 4: Force shutdown if necessary forceShutdownIfNeeded(); System.out.println("=== Graceful Shutdown Completed ==="); } private void stopAcceptingNewRequests() { System.out.println("Stopping acceptance of new requests..."); // Implementation depends on your load balancer/API gateway } private void waitForOngoingRequests() { System.out.println("Waiting for ongoing requests to complete..."); try { // Monitor active requests and wait CompletableFuture<Void> completion = monitorActiveRequests(); completion.get(30, java.util.concurrent.TimeUnit.SECONDS); } catch (Exception e) { System.err.println("Error waiting for requests to complete: " + e.getMessage()); } } private CompletableFuture<Void> monitorActiveRequests() { return CompletableFuture.runAsync(() -> { // Implement request monitoring logic // This is application-specific try { Thread.sleep(10000); // Example: wait 10 seconds } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } private void closeResources() { System.out.println("Closing resources..."); // Close database connections, file handles, network connections, etc. } private void forceShutdownIfNeeded() { System.out.println("Initiating force shutdown if needed..."); // Force kill after timeout } }

Testing Graceful Shutdown

1. Integration Test

import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GracefulShutdownTest { @LocalServerPort private int port; @Test public void testGracefulShutdown() throws InterruptedException { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:" + port + "/api/health"; // Test that application responds normally ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); assertTrue(response.getStatusCode().is2xxSuccessful()); // In a real test, you would trigger shutdown and verify behavior // This is simplified for demonstration } }

2. Manual Testing Script

#!/bin/bash # graceful-shutdown-test.sh echo "Starting Spring Boot application..." java -jar your-application.jar & APP_PID=$! echo "Application started with PID: $APP_PID" # Wait for application to start sleep 30 echo "Sending shutdown signal..." curl -X POST http://localhost:8080/actuator/shutdown # Wait for graceful shutdown wait $APP_PID echo "Application shutdown complete"

Best Practices

1. Configuration Recommendations

# Optimal graceful shutdown configuration server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s spring.datasource.hikari.max-lifetime=1200000 management.endpoints.web.exposure.include=health,info,metrics,shutdown

2. Monitoring and Logging

import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component public class ShutdownMonitor { private static final Logger logger = LoggerFactory.getLogger(ShutdownMonitor.class); public void logShutdownProgress(String phase) { logger.info("Shutdown phase: {}", phase); // Add metrics collection here } }

3. Health Check Integration

import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class ShutdownHealthIndicator implements HealthIndicator { private final GracefulShutdownHandler shutdownHandler; public ShutdownHealthIndicator(GracefulShutdownHandler shutdownHandler) { this.shutdownHandler = shutdownHandler; } @Override public Health health() { if (shutdownHandler.isShutdownInProgress()) { return Health.down() .withDetail("message", "Application is shutting down") .build(); } return Health.up().build(); } }

Conclusion

Implementing graceful shutdown in Spring Boot ensures that your application can handle termination signals properly, complete ongoing work, and maintain data integrity. The key aspects include:

  1. Configuration: Enable graceful shutdown in properties
  2. Resource Management: Properly close connections and thread pools
  3. Request Handling: Complete ongoing requests and reject new ones
  4. Monitoring: Track shutdown progress and health status
  5. Testing: Verify shutdown behavior in different scenarios

By following these practices, you can ensure your Spring Boot applications shutdown gracefully without data loss or corruption.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper