Why kill -9 Can Corrupt Your Spring Boot Service and How to Shut It Down Gracefully
This article explains the dangers of using the Linux kill -9 command on Spring Boot applications, illustrates data loss scenarios, and provides multiple graceful shutdown methods—including SIGTERM, Spring's ConfigurableApplicationContext, Actuator endpoints, and custom Tomcat shutdown configurations—complete with code examples and best‑practice tips.
kill -9 pid ???
The kill command sends signals to processes; the default is SIGTERM (15) which terminates a program, while SIGKILL (9) forces immediate termination. Process IDs can be obtained with ps or jobs.
Problems caused by kill -9 pid
Because kill -9 is a violent termination, it can lead to serious consequences such as data inconsistency. For example, in a money‑transfer operation using MyISAM tables, if the process is killed after debiting account A but before crediting account B, the system ends up with lost money, similar to a sudden power outage.
How to stop a project gracefully
Tomcat provides shutdown scripts ( shutdown.bat/shutdown.sh) that perform an orderly shutdown. A graceful shutdown involves four steps: stop accepting new requests, check for active threads, wait for running threads to finish, and finally stop the container.
Graceful shutdown with kill -15 pid
Sending SIGTERM (15) allows the application to handle interruption. The example creates a @GetMapping("/test") endpoint that sleeps for 100 seconds, then demonstrates stopping the process with kill -15 <pid>. The log shows that the start message is printed, the thread is interrupted, and the end message appears.
@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";
}ConfigurableApplicationContext close
Implementing a shutdown endpoint that casts the Spring ApplicationContext to ConfigurableApplicationContext and calls close() stops the Spring Boot application.
package com.ymy.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
@PostMapping(value = "shutdown")
public void shutdown() {
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) context;
ctx.close();
}
}Actuator shutdown
Adding the spring-boot-starter-actuator dependency and enabling the shutdown endpoint in application.yml provides a RESTful way to stop the service.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> server:
port: 9988
management:
endpoints:
web:
exposure:
include: shutdown
endpoint:
shutdown:
enabled: trueElegantShutdownConfig
A custom Tomcat connector customizer pauses new requests, waits for the thread pool to finish, and optionally forces termination after a configurable wait time.
package com.ymy.config;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ElegantShutdownConfig implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
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();
var executor = connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) executor;
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(waitTime, TimeUnit.SECONDS)) {
System.out.println("Please try forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}Data backup with @PreDestroy
Using the @PreDestroy annotation on a bean method allows execution of backup logic just before the Spring container shuts down.
package com.ymy.config;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
@Configuration
public class DataBackupConfig {
@PreDestroy
public void backData() {
System.out.println("Backing up data......");
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
