Backend Development 21 min read

Graceful Shutdown of Spring Boot Applications: Avoiding kill -9 Pitfalls

This article explains why using the Linux kill -9 command to stop Java services can cause data loss, demonstrates the differences between forceful and graceful termination, and provides multiple Spring Boot shutdown solutions—including kill -15, actuator endpoints, custom Tomcat connectors, and @PreDestroy hooks—complete with code examples.

Top Architect
Top Architect
Top Architect
Graceful Shutdown of Spring Boot Applications: Avoiding kill -9 Pitfalls

The kill -9 pid command sends the SIGKILL signal to terminate a process immediately, which can lead to serious issues such as data inconsistency in databases (especially MyISAM) and incomplete distributed transactions, effectively simulating a sudden power outage.

Because SIGKILL does not allow the application to release resources or finish ongoing work, using it on services that handle critical operations (e.g., money transfers) may leave the system in an unrecoverable state.

To stop a service gracefully, the recommended approach is to send SIGTERM (default) or SIGINT, or use kill -15 pid , which gives the JVM a chance to interrupt threads, run shutdown hooks, and close resources in an orderly fashion.

Example: a simple Spring Boot controller that sleeps for 100 seconds. When the endpoint /test is called, the thread logs "test — start", sleeps, then logs "test — end". Sending kill -15 <pid> interrupts the sleep, causing an InterruptedException but still allowing the finally block to execute and the log to show the end message.

@GetMapping("/test")
public String test(){
    log.info("test --- start");
    try{ Thread.sleep(100000); }
    catch(InterruptedException e){ e.printStackTrace(); }
    log.info("test --- end");
    return "test";
}

Spring Boot also provides an actuator endpoint for shutdown. By adding spring-boot-starter-actuator and exposing the /shutdown endpoint in application.yml , you can stop the application via an HTTP request, which triggers the same graceful shutdown sequence.

server:
  port: 9988
management:
  endpoints:
    web:
      exposure:
        include: shutdown
  endpoint:
    shutdown:
      enabled: true

For more control, you can implement a custom Tomcat connector customizer that pauses new requests, shuts down the thread pool, and waits a configurable period before forcing termination. The following class demonstrates this pattern:

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
{
    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 pool = (ThreadPoolExecutor) executor;
            pool.shutdown();
            try {
                if (!pool.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                    System.out.println("Attempting forced shutdown");
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Register this bean in the main application class and expose it to Tomcat:

@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(ElegantShutdownConfig config) {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addConnectorCustomizers(config);
        return tomcat;
    }
}

If you need to execute custom logic (e.g., data backup) before the JVM shuts down, annotate a method with @PreDestroy . This method runs after all Spring beans are still available but before the container is destroyed.

@PreDestroy
public void backupBeforeShutdown(){
    // perform backup or logging here
}

By combining these techniques—using SIGTERM/SIGINT, actuator shutdown, custom Tomcat graceful shutdown, and @PreDestroy hooks—you can stop Spring Boot services safely without risking data loss or abrupt thread termination.

backendjavaLinuxSpring BootGraceful ShutdownKill Command
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.