Why kill -9 Can Corrupt Your Data and How to Gracefully Shut Down Spring Boot
The article explains the dangers of using kill -9 to terminate Java processes, illustrates how it can cause data loss in database operations, and provides several practical approaches—including SIGTERM, ConfigurableApplicationContext.close(), Spring Boot Actuator, and a custom Tomcat shutdown hook—to gracefully stop Spring Boot services while preserving data integrity.
Background: kill -9 and Its Risks
The Linux kill command sends signals to processes; the default is SIGTERM (15). When a process does not stop, SIGKILL (9) is often used. Because SIGKILL forcefully terminates a process, it is equivalent to a sudden power outage and can leave databases in an inconsistent state, especially with non‑transactional storage engines such as MyISAM.
Problem: Data Inconsistency When Using kill -9
In a money‑transfer scenario, if a process handling debit and credit operations is killed with SIGKILL, the debit may be persisted while the credit is lost, leading to unrecoverable financial errors. Distributed services face similar risks, making SIGKILL unsuitable for production shutdowns.
Graceful Shutdown Strategies
1. Using SIGTERM (kill -15)
Sending SIGTERM allows the JVM to interrupt running threads, giving them a chance to finish cleanly. The following Spring Boot controller demonstrates the effect:
@GetMapping("/test")
public String test(){
log.info("test --- start");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("test --- end");
return "test";
}Steps:
Start the application (e.g., sudo mvn spring-boot:run).
Find the process ID with sudo ps -ef | grep shutdown.
Trigger the endpoint ( curl 127.0.0.1:9988/test) to put the thread to sleep.
Terminate with sudo kill -15 <pid>.
The log shows an InterruptedException from the sleeping thread, followed by normal shutdown messages, confirming that the thread was allowed to finish.
2. Closing the Application Context Programmatically
Implement ApplicationContextAware and invoke ConfigurableApplicationContext.close():
package com.ymy.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
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) throws BeansException {
this.context = applicationContext;
}
@GetMapping("/test")
public String test(){
log.info("test --- start");
try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("test --- end");
return "test";
}
@PostMapping("/shutdown")
public void shutdown(){
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) context;
ctx.close();
}
}The close() method removes the JVM shutdown hook and triggers a graceful termination of the Spring context.
3. Using Spring Boot Actuator Shutdown Endpoint
Add the actuator dependency and enable the shutdown endpoint:
<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: trueCalling curl -X POST http://localhost:8888/actuator/shutdown returns a friendly message and stops the service after completing in‑flight requests.
4. Custom Elegant Shutdown with Tomcat
Define a TomcatConnectorCustomizer that pauses the connector and waits for thread‑pool termination:
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.Executor;
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; // seconds
@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();
try {
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
System.out.println("Please consider forceful shutdown");
}
} catch (InterruptedException ex) {
System.out.println("Interrupted during shutdown");
Thread.currentThread().interrupt();
}
}
}
}Register the config as a bean and customize the embedded Tomcat factory:
package com.ymy;
import com.ymy.config.ElegantShutdownConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ShutdownServerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(ShutdownServerApplication.class, args);
ctx.registerShutdownHook();
}
@Bean
public ElegantShutdownConfig elegantShutdownConfig() {
return new ElegantShutdownConfig();
}
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(elegantShutdownConfig());
return tomcat;
}
}When the application receives a shutdown signal, it pauses new requests, waits up to waitTime seconds for active tasks to finish, and then stops without throwing exceptions.
5. Executing Cleanup Tasks on Shutdown
Use the @PreDestroy annotation to run code before the container stops, for example to back up data:
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 before shutdown...");
}
}This method is invoked automatically during the graceful shutdown sequence, allowing developers to perform finalization tasks such as data backup or logging.
Conclusion
Using kill -9 on a Java service can cause abrupt termination, data loss, and hard‑to‑debug errors. Instead, adopt one of the graceful shutdown techniques described above—SIGTERM, programmatic context closing, Actuator’s shutdown endpoint, or a custom Tomcat shutdown hook—to ensure in‑flight requests complete, resources are released cleanly, and optional cleanup logic runs safely.
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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
