Graceful Shutdown of Spring Boot Applications: Risks of kill -9 and Proper Termination Techniques

This article explains why using the forceful kill -9 command to stop Linux processes can cause data loss and inconsistent states, especially in distributed systems, and demonstrates several graceful shutdown methods for Spring Boot—including SIGTERM, ConfigurableApplicationContext.close(), Actuator shutdown endpoints, custom Tomcat connector handling, and @PreDestroy data‑backup hooks—complete with code examples and execution logs.

Java Captain
Java Captain
Java Captain
Graceful Shutdown of Spring Boot Applications: Risks of kill -9 and Proper Termination Techniques

kill -9 pid ???

The kill command sends a signal to a process; by default it sends SIGTERM (15) to terminate the program, and if that fails you can use SIGKILL (9) to force termination. The process ID can be obtained with ps or jobs.

In simple terms, kill -9 pid forcefully kills a Linux process, which can be dangerous in production environments.

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, if a money‑transfer operation is interrupted, InnoDB would roll back safely, but MyISAM would leave one account debited and the other unchanged, effectively simulating a power outage.

In distributed systems, using kill -9 to stop a service bypasses transaction guarantees and may cause unrecoverable data loss.

Even with MyISAM, partial updates (e.g., updating only one of two related tables) can cause inconsistent user information, which is unacceptable for critical data.

Therefore, you should avoid using kill -9 to stop services and instead use graceful shutdown mechanisms.

Graceful termination of services

Step 1: Stop accepting new requests and internal threads. Step 2: Check if any threads are still running. Step 3: Wait for running threads to finish. Step 4: Stop the container.

Below are several graceful shutdown solutions for Spring Boot.

Graceful shutdown

kill -15 pid

Sending SIGTERM (15) allows the JVM to interrupt sleeping threads and shut down cleanly.

@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";
}
1234567891011

When the process is killed with kill -15 14086, the thread receives an interrupt, the sleep throws InterruptedException, and the method finishes, printing "test --- end".

application.yml

server:
  port: 9988
12

Start the project

sudo mvn spring-boot:run
1

Find the process ID:

sudo ps -ef | grep shutdown
1

Test the endpoint and then stop the process:

sudo curl 127.0.0.1:9988/test
1
sudo kill -15 14086
1

Log output shows the interrupt and the method completing, confirming a graceful shutdown.

ConfigurableApplicationContext close

Implement a controller that injects the application context and calls close() on it.

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(value = "/test")
    public String test() {
        log.info("test --- start");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("test --- end");
        return "test";
    }

    /**
     * Shutdown
     */
    @PostMapping(value = "shutdown")
    public void shutdown() {
        ConfigurableApplicationContext cyx = (ConfigurableApplicationContext) context;
        cyx.close();
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344

The call to cyx.close() triggers the JVM shutdown hook, allowing Spring Boot to stop gracefully.

Actuator shutdown endpoint

Add the Actuator dependency and enable the shutdown endpoint in application.yml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
12345
server:
  port: 9988

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

Expose a custom shutdown URL and invoke it while a request is in progress. The response confirms the shutdown, and the logs show the interrupt handling similar to the previous methods.

Custom Tomcat graceful shutdown (ElegantShutdownConfig)

Define a Tomcat connector customizer that pauses the connector and waits for the thread pool to finish:

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;

    @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) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                    System.out.println("请尝试暴力关闭");
                }
            } catch (InterruptedException ex) {
                System.out.println("异常了");
                Thread.currentThread().interrupt();
            }
        }
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041

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

package com.ymy;

import com.ymy.config.ElegantShutdownConfig;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
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 run = SpringApplication.run(ShutdownServerApplication.class, args);
        run.registerShutdownHook();
    }

    @Bean
    public ElegantShutdownConfig elegantShutdownConfig() {
        return new ElegantShutdownConfig();
    }

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addConnectorCustomizers(elegantShutdownConfig());
        return tomcat;
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041

When the /test endpoint is sleeping, the server now waits the configured waitTime (10 seconds) before terminating, avoiding abrupt thread interruption.

Data backup during shutdown

You can execute backup logic before the container stops by adding a method annotated with @PreDestroy:

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("正在备份数据。。。。。。。。。。。");
    }
}
123456789101112131415

After enabling this configuration, the console prints the backup message when the application shuts down.

PS: If you find this sharing useful, feel free to like and follow.

END

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaLinuxspring-bootGraceful Shutdownkill -9
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

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.