SoFunction
Updated on 2025-04-14

Three ways to achieve elegant downtime in SpringBoot

introduction

Start and stop of an application is a common operation. However, abruptly terminating a running application may result in failure of the request being processed, inconsistent data, and other problems. Graceful Shutdown refers to the process in which an application can properly handle existing requests, release resources, and then exit after receiving the stop signal. This article will introduce in detail three ways to achieve elegant downtime in SpringBoot.

What is elegant downtime?

Elegant downtime means that the application does not terminate immediately after receiving the stop signal, but rather exits in an orderly manner following the following steps:

  • Stop receiving new requests
  • Wait for the request being processed to complete
  • Close various resource connections (database connection pool, thread pool, message queue connection, etc.)
  • Complete the necessary cleaning work
  • Finally exit the application

The core value of elegant downtime is:

  • Improve user experience and avoid sudden interruptions in requests
  • Ensure data consistency and prevent data loss

Method 1: SpringBoot built-in elegant downtime support

Principles and Supported Versions

Starting with Spring Boot 2.3, the framework natively supports elegant downtime mechanisms. This is the easiest and officially recommended way to implement it.

When an application receives a stop signal (such as SIGTERM), the embedded web server (such as Tomcat, Jetty, or Undertow) performs the following steps:

  • Stop receiving new connection requests
  • Set keepalive of existing connection to false
  • Wait for all active request processing to complete or timeout
  • Close application context and related resources

Configuration method

existorAdd simple configuration to enable:

server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Heretimeout-per-shutdown-phaseSpecifies the maximum time to wait for the active request to complete, the default is 30 seconds.

Implementation example

Here is a complete SpringBoot application example with elegant downtime enabled:

@SpringBootApplication
public class GracefulShutdownApplication {

    private static final Logger logger = ();

    public static void main(String[] args) {
        (, args);
        ("Application started");
    }
    
    @RestController
    @RequestMapping("/api")
    static class SampleController {
        
        @GetMapping("/quick")
        public String quickRequest() {
            return "Quick response";
        }
        
        @GetMapping("/slow")
        public String slowRequest() throws InterruptedException {
            // Simulate long-term processing requests            ("Start processing slow request");
            (10000); // 10 seconds            ("Finished processing slow request");
            return "Slow response completed";
        }
    }
    
    @Bean
    public ApplicationListener<ContextClosedEvent> contextClosedEventListener() {
        return event -> ("Received spring context closed event - shutting down");
    }
}

Test verification

  • Start the application
  • Initiate a long-running request:curl http://localhost:8080/api/slow
  • During processing, send SIGTERM signal to the application:kill -15 <Process ID>, If it is an IDEA development environment, you can click the Stop Service button once
  • Observe log output: You should see that the application is waiting for the long request processing to be completed before closing

Pros and cons

advantage:

  • Simple configuration, official native support
  • No extra code required, low maintenance cost
  • Suitable for most web application scenarios
  • Perfect integration with Spring lifecycle events

shortcoming:

  • Only support Spring Boot 2.3+ version
  • Additional processing is required for scenarios that exceed HTTP requests (such as long-running jobs)
  • Relatively low flexibility, no fine control of downtime
  • Only a unified timeout time can be set

Applicable scenarios

  • Spring Boot 2.3+ version of web application
  • The request processing time is expected, and there will be no requests running for a long time.
  • Standard services in microservice architecture

Method 2: Use Actuator endpoint to achieve elegant shutdown

Principles and Implementation

Spring Boot Actuator provides a rich operation and maintenance endpoint, includingshutdownEndpoint, which can be used to trigger an elegant downtime of the application. This approach is unique in that it allows the downtime process to be triggered via HTTP requests, suitable for scenarios where remote operations are required.

Configuration steps

  • Add Actuator dependencies:
<dependency>
    <groupId></groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • Enable and expose shutdown endpoint:
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown
      base-path: /management
  server:
    port: 8081  # Optional: Set a separate port for the management endpoint

How to use

Triggering downtime via HTTP POST request:

curl -X POST http://localhost:8081/management/shutdown

After the request is successful, a response like the following will be returned:

{
  "message": "Shutting down, bye..."
}

Safety considerations

Since shutdown is a sensitive operation, security must be considered:

spring:
  security:
    user:
      name: admin
      password: secure_password
      roles: ACTUATOR

management:
  endpoints:
    web:
      exposure:
        include: shutdown
  endpoint:
    shutdown:
      enabled: true

# Configure endpoint security-path: /management

Access methods after using security configuration:

curl -X POST http://admin:secure_password@localhost:8080/management/shutdown

Complete implementation example

@SpringBootApplication
@EnableWebSecurity
public class ActuatorShutdownApplication {

    private static final Logger logger = ();

    public static void main(String[] args) {
        (, args);
        ("Application started with Actuator shutdown enabled");
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        ().disable()
            .authorizeHttpRequests()
            .requestMatchers("/management/**").hasRole("ACTUATOR")
            .anyRequest().permitAll()
            .and()
            .httpBasic();
        
        return ();
    }
    
    @RestController
    static class ApiController {
        
        @GetMapping("/api/hello")
        public String hello() {
            return "Hello, world!";
        }
    }
    
    @Bean
    public ApplicationListener&lt;ContextClosedEvent&gt; shutdownListener() {
        return event -&gt; {
            ("Received shutdown signal via Actuator");
            
            // Wait for the active request to complete            ("Waiting for active requests to complete...");
            try {
                (5000); // Simplify the example, the active request should be monitored in fact            } catch (InterruptedException e) {
                ().interrupt();
            }
            
            ("All requests completed, shutting down");
        };
    }
}

Pros and cons

advantage:

  • Can remotely trigger shutdown via HTTP request
  • Suitable for management tools and operation and maintenance script integration
  • Access control can be achieved in integration with Spring Security
  • Supports all Spring Boot versions (including versions before 2.3)

shortcoming:

  • Requires additional configuration and dependencies
  • Potential security risks require careful protection of endpoints
  • Additional encoding is required for the shutdown of internal complex resources

Applicable scenarios

  • Scenarios where downtime is required through HTTP request
  • Manage application deployment using operation and maintenance automation tools
  • Services need to be stopped in a specific order in a cluster environment
  • Internal management system needs to directly control the application life cycle

Method 3: Custom ShutdownHook to achieve elegant shutdown

Principles and Implementation

The JVM provides a ShutdownHook mechanism that allows custom logic to be executed before the JVM is closed. By registering a custom ShutdownHook, we can achieve more granular and flexible elegant downtime control. The advantage of this method is that it can accurately control the resource release order and is suitable for applications with complex resource management needs.

Basic implementation steps

  • Create a custom ShutdownHandler class
  • Register JVM ShutdownHook
  • Implement custom elegant shutdown logic in Hook

Complete implementation example

Here is a complete example with detailed comments:

@SpringBootApplication
public class CustomShutdownHookApplication {

    private static final Logger logger = ();

    public static void main(String[] args) {
        ConfigurableApplicationContext context = (, args);
        
        // Register a custom ShutdownHook        registerShutdownHook(context);
        
        ("Application started with custom shutdown hook");
    }
    
    private static void registerShutdownHook(ConfigurableApplicationContext context) {
        ().addShutdownHook(new Thread(() -&gt; {
            ("Executing custom shutdown hook");
            try {
                // 1. Stop receiving new requests (if it is a web application)                if (("tomcatServletWebServerFactory")) {
                    TomcatServletWebServerFactory server = ();
                    ("Stopping web server to reject new requests");
                    // Note: In actual applications, you need to find the correct way to stop a specific web server                }
                
                // 2. Wait for the active request processing to complete                ("Waiting for active requests to complete");
                // Custom waiting logic can be added here, such as checking the number of active connections or thread status                (5000); // Simplify the example                
                // 3. Close the custom thread pool                shutdownThreadPools(context);
                
                // 4. Close the message queue connection                closeMessageQueueConnections(context);
                
                // 5. Close the database connection pool                closeDataSourceConnections(context);
                
                // 6. Execute other custom cleanup logic                performCustomCleanup(context);
                
                // 7. Finally close Spring context                ("Closing Spring application context");
                ();
                
                ("Graceful shutdown completed");
            } catch (Exception e) {
                ("Error during graceful shutdown", e);
            }
        }, "GracefulShutdownHook"));
    }
    
    private static void shutdownThreadPools(ApplicationContext context) {
        ("Shutting down thread pools");
        
        // Get all ExecutorService types beans        Map&lt;String, ExecutorService&gt; executors = ();
        
        ((name, executor) -&gt; {
            ("Shutting down executor: {}", name);
            ();
            try {
                // Wait for the task to be completed                if (!(15, )) {
                    ("Executor did not terminate in time, forcing shutdown: {}", name);
                    ();
                }
            } catch (InterruptedException e) {
                ().interrupt();
                ("Interrupted while waiting for executor shutdown: {}", name);
                ();
            }
        });
    }
    
    private static void closeMessageQueueConnections(ApplicationContext context) {
        ("Closing message queue connections");
        
        // Example: Close RabbitMQ connection        if (("rabbitConnectionFactory")) {
            try {
                ConnectionFactory rabbitFactory = ();
                // Close the connection appropriately                ("Closed RabbitMQ connections");
            } catch (Exception e) {
                ("Error closing RabbitMQ connections", e);
            }
        }
        
        // Example: Close Kafka connection        if (("kafkaConsumerFactory")) {
            try {
                // Close the code for Kafka connection                ("Closed Kafka connections");
            } catch (Exception e) {
                ("Error closing Kafka connections", e);
            }
        }
    }
    
    private static void closeDataSourceConnections(ApplicationContext context) {
        ("Closing datasource connections");
        
        // Get all DataSource types beans        Map&lt;String, DataSource&gt; dataSources = ();
        
        ((name, dataSource) -&gt; {
            try {
                // For HikariCP connection pool                if (dataSource instanceof HikariDataSource) {
                    ((HikariDataSource) dataSource).close();
                    ("Closed HikariCP datasource: {}", name);
                }
                // You can add shutdown logic for other types of connection pools                else {
                    // Try to call the close method through reflection                    Method closeMethod = ().getMethod("close");
                    (dataSource);
                    ("Closed datasource: {}", name);
                }
            } catch (Exception e) {
                ("Error closing datasource: {}", name, e);
            }
        });
    }
    
    private static void performCustomCleanup(ApplicationContext context) {
        // Here you can add application-specific cleaning logic        ("Performing custom cleanup tasks");
        
        // For example: save application status        // For example: Release local resources        // For example: Send a shutdown notification to other systems    }
    
    @Bean
    public ExecutorService applicationTaskExecutor() {
        return (10);
    }
    
    @RestController
    @RequestMapping("/api")
    static class ApiController {
        
        @Autowired
        private ExecutorService applicationTaskExecutor;
        
        @GetMapping("/task")
        public String submitTask() {
            (() -&gt; {
                try {
                    ("Task started, will run for 30 seconds");
                    (30000);
                    ("Task completed");
                } catch (InterruptedException e) {
                    ("Task interrupted");
                    ().interrupt();
                }
            });
            return "Task submitted";
        }
    }
}

Pros and cons

advantage:

  • Maximum flexibility and customizability
  • You can accurately control the order and method of resource closure
  • Suitable for complex application scenarios and all Spring Boot versions
  • Can handle external resources that Spring framework cannot manage

shortcoming:

  • High complexity of implementation, need to know the application resources in detail
  • High maintenance costs
  • Prone to problems caused by resource closure order errors

Applicable scenarios

  • Applications with complex resource management requirements
  • Scenarios where resources need to be closed in a specific order
  • Using Spring Boot earlier versions (built-in elegant downtime is not supported)
  • Non-Web application or hybrid application architecture
  • Resources that are not directly managed by Spring framework (such as Native resources)

Program comparison and selection guide

The following is a comparison table of three solutions to help you choose the implementation method that best suits your scenario:

Features/Scheme SpringBoot built-in Actuator endpoint Custom ShutdownHook
Implement complexity Low middle high
flexibility Low middle high
Customizable Low middle high
Framework dependency Spring Boot 2.3+ Any Spring Boot version Any Java application
Extra dependencies none Actuator none
Trigger method System Signal (SIGTERM) HTTP Request System signals or customizations
Safety considerations Low High (need to protect endpoints) middle
Maintenance cost Low middle high
Applicable to web applications Best for Suitable Suitable
Applicable to non-web applications Some suitable Some suitable Best for
  • Choose SpringBoot built-in solution,if:

    • Using Spring Boot Version 2.3+
    • Mainly standard web applications
    • No special resource management requirements
    • Hope the easiest configuration
  • Select Actuator endpoint scheme,if:

    • The shutdown needs to be triggered via HTTP request
    • Using earlier Spring Boot versions
    • Integrated operation and maintenance automation tools
    • Already monitoring using Actuator
  • Select a custom ShutdownHook solution,if:

    • Have complex resource management requirements
    • Precise control of downtime processes is required
    • Resources that are not directly managed by Spring framework
    • Hybrid architecture applications (both web and background jobs)

in conclusion

Elegant downtime is an important practice to ensure application reliability and user experience. SpringBoot provides a variety of implementation methods, from simple configuration to complex custom implementations, which can meet the needs of different application scenarios.

  • For simple applications: SpringBoot built-in solution is the best choice, with simple configuration, enough to meet the needs of most web applications
  • For scenarios that require remote triggering: Actuator endpoint provides HTTP interface control, which facilitates integrated operation and maintenance system
  • For complex applications: Custom ShutdownHook provides maximum flexibility to accurately control the order and way of resource release

Elegant downtime should be a standard practice in microservice design, no matter which method you choose. Correctly achieving elegant downtime can not only improve system stability, but also improve user experience and reduce business interruptions caused by application restarts or downgrades.

The above is the detailed content of the three ways to achieve elegant shutdown in SpringBoot. For more information about SpringBoot's elegant shutdown, please pay attention to my other related articles!