Graceful Shutdown of Spring Boot Applications: Avoiding kill -9 and Using Actuator, Shutdown Hooks, and Custom Configurations
This article explains why using the violent kill -9 command can corrupt data in Java services, describes the four‑step graceful shutdown process, and provides multiple Spring Boot solutions—including SIGTERM, Actuator shutdown endpoint, custom Tomcat connector, and @PreDestroy data‑backup hooks—to stop services safely while handling thread interruptions.
kill -9 pid ???
In Linux, the SIGTERM(15) signal terminates a process gracefully, while SIGKILL(9) forces an immediate termination; the latter is equivalent to a sudden power loss and can cause data loss, especially with non‑transactional storage engines such as MyISAM.
Using kill -9 pid in production may lead to inconsistent database states, lost money transfers, or corrupted user information because the operation aborts ongoing transactions without cleanup.
Problems Caused by kill -9 pid
When a service handling a financial transfer is killed abruptly, one side of a two‑step operation may be completed while the other is not, resulting in unrecoverable loss. Distributed systems face similar risks, and even though most use InnoDB, the abrupt termination still interrupts thread pools and can leave resources hanging.
Graceful Shutdown Process
The proper shutdown consists of four steps: stop receiving new requests, check for active threads, wait for those threads to finish, and finally stop the container.
Graceful Shutdown with kill -15
kill -15 pid
A SIGTERM signal ( kill -15 pid ) allows the JVM to interrupt sleeping threads, giving them a chance to finish. The following example demonstrates this behavior in a Spring Boot controller.
@GetMapping(value = "/test")
public String test() {
log.info("test --- start");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("test --- end");
return "test";
}Running the application, invoking curl 127.0.0.1:9988/test , and then executing kill -15 14086 results in the thread being interrupted, the InterruptedException being logged, and the "test — end" message still being printed.
ConfigurableApplicationContext close
Calling ConfigurableApplicationContext.close() removes the JVM shutdown hook and stops the Spring context, which effectively shuts down the service.
public void close() {
synchronized (this.startupShutdownMonitor) {
this.doClose();
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
} catch (IllegalStateException var4) {}
}
}
}Actuator Shutdown Endpoint
Adding the Spring Boot Actuator dependency and exposing the shutdown endpoint allows a REST call to stop the application gracefully.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>Configuration in application.yml enables the endpoint:
management:
endpoints:
web:
exposure:
include: shutdown
endpoint:
shutdown:
enabled: true
server:
port: 8888Calling the shutdown URL returns a friendly message and triggers the same interrupt‑based termination as kill -15 .
Custom Delayed Shutdown (ElegantShutdownConfig)
A custom Tomcat connector can pause new requests and wait for a configurable period (e.g., 10 seconds) before forcing termination.
class ElegantShutdownConfig implements TomcatConnectorCustomizer, ApplicationListener
{
private volatile Connector connector;
private final int waitTime = 10;
@Override
public void customize(Connector connector) { this.connector = connector; }
@Override
public void onApplicationEvent(ContextClosedEvent event) {
connector.pause();
Executor executor = connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
System.out.println("请尝试暴力关闭");
}
}
}
}The bean is registered in the main application class and the Tomcat factory is customized to use it.
@Bean
public ElegantShutdownConfig elegantShutdownConfig() { return new ElegantShutdownConfig(); }
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(elegantShutdownConfig());
return tomcat;
}With this configuration, the service waits the specified time before shutting down, avoiding abrupt thread interruption.
Data Backup on Shutdown
Adding a method annotated with @PreDestroy allows execution of custom logic (e.g., data backup) just before the Spring container stops.
@Configuration
public class DataBackupConfig {
@PreDestroy
public void backData() {
System.out.println("正在备份数据。。。。。。。。。。。");
}
}When the application is stopped using any of the graceful methods above, the backup message is printed, confirming that the hook runs.
Overall, avoiding kill -9 and using the described graceful shutdown techniques ensures data consistency, proper resource release, and the opportunity to perform cleanup tasks such as backups.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.