Backend Development 13 min read

Graceful Shutdown of Spring Boot Applications: kill Commands, Signal Handlers, and Runtime Hooks

This article explains how to achieve graceful shutdown for Java applications by using Linux kill signals, JVM SignalHandler registration, Runtime.addShutdownHook, and Spring Boot's actuator shutdown endpoint, providing code examples and configuration details for safe resource cleanup.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Graceful Shutdown of Spring Boot Applications: kill Commands, Signal Handlers, and Runtime Hooks

In this technical guide, the author introduces the concept of graceful shutdown for Java services, emphasizing the importance of releasing resources and avoiding data loss when terminating processes.

What Is Graceful Shutdown

Graceful shutdown ensures that an application notifies its threads, sockets, registration centers, temporary files, and memory allocations to clean up before the process exits, preventing data inconsistency in distributed environments.

kill Command

The kill -9 pid command simulates a forced termination, while kill -15 pid sends a termination signal that allows the application to shut down cleanly.

<code># View JVM process PID
jps
# List all signal names
kill -l
# Windows signal constants
# INT  SIGINT   2   Ctrl+C interrupt
# ILL  SIGILL   4   Illegal instruction
# FPE  SIGFPE   8   Floating point exception
# SEGV SIGSEGV 11   Segment violation
# TERM SIGTERM 5   Software termination signal
# BREAK SIGBREAK 21 Ctrl-Break sequence
# ABRT SIGABRT 22   Abnormal termination (abort)
# Linux signal constants
# HUP  SIGHUP   1   Terminal hangup
# INT  SIGINT   2   Interrupt (Ctrl+C)
# QUIT SIGQUIT  3   Quit (Ctrl+\)
# KILL SIGKILL  9   Force kill
# TERM SIGTERM 15   Terminate
# CONT SIGCONT 18   Continue (fg/bg)
# STOP SIGSTOP 19   Stop (Ctrl+Z)</code>

JVM Signal Handling

The JVM registers a custom SignalHandler at startup to process Linux signals. When a signal is received, the handler invokes Shutdown.exit with the appropriate exit code.

<code>public interface SignalHandler {
    SignalHandler SIG_DFL = new NativeSignalHandler(0L);
    SignalHandler SIG_IGN = new NativeSignalHandler(1L);
    void handle(Signal var1);
}

class Terminator {
    private static SignalHandler handler = null;
    static void setup() {
        if (handler == null) {
            SignalHandler var0 = new SignalHandler() {
                public void handle(Signal var1) {
                    Shutdown.exit(var1.getNumber() + 128);
                }
            };
            handler = var0;
            try { Signal.handle(new Signal("INT"), var0); } catch (IllegalArgumentException e) {}
            try { Signal.handle(new Signal("TERM"), var0); } catch (IllegalArgumentException e) {}
        }
    }
}</code>

Runtime.addShutdownHook

Java provides Runtime.getRuntime().addShutdownHook(Thread hook) to register a hook that runs when the JVM shuts down. The hook can perform cleanup tasks before the process terminates.

<code>public class Runtime {
    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }
}

class ApplicationShutdownHooks {
    private static IdentityHashMap<Thread, Thread> hooks;
    static synchronized void add(Thread hook) {
        if (hooks == null) throw new IllegalStateException("Shutdown in progress");
        if (hook.isAlive()) throw new IllegalArgumentException("Hook already running");
        if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered");
        hooks.put(hook, hook);
    }
}

class Shutdown {
    private static final int RUNNING = 0, HOOKS = 1, FINALIZERS = 2;
    private static int state = RUNNING;
    private static final Runnable[] hooks = new Runnable[10];
    static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null) throw new InternalError("Shutdown hook at slot " + slot + " already registered");
            if (!registerShutdownInProgress) {
                if (state > RUNNING) throw new IllegalStateException("Shutdown in progress");
            } else {
                if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
            }
            hooks[slot] = hook;
        }
    }
    // ... (other shutdown sequence methods) ...
}</code>

Spring Context Shutdown

Spring registers its own shutdown hook. When the context closes, it publishes a ContextClosedEvent , invokes LifecycleProcessor.onClose() , stops beans, destroys the BeanFactory, and runs custom hooks.

<code>public abstract class AbstractApplicationContext extends DefaultResourceLoader {
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread() {
                @Override public void run() { doClose(); }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }
    protected void doClose() {
        boolean actuallyClose;
        synchronized (this.activeMonitor) {
            actuallyClose = this.active && !this.closed;
            this.closed = true;
        }
        if (actuallyClose) {
            logger.info("Closing " + this);
            publishEvent(new ContextClosedEvent(this));
            try { getLifecycleProcessor().onClose(); } catch (Throwable ex) { logger.warn(..., ex); }
            destroyBeans();
            closeBeanFactory();
            onClose();
            synchronized (this.activeMonitor) { this.active = false; }
        }
    }
}

interface LifecycleProcessor extends Lifecycle {
    void onRefresh();
    void onClose();
}</code>

Spring Boot Actuator Shutdown Endpoint

Spring Boot provides a RESTful shutdown endpoint via the spring-boot-starter-actuator module. Enabling it and sending a POST request to /shutdown triggers the same context close logic.

<code># Enable shutdown endpoint
endpoints.shutdown.enabled=true
# Disable authentication (for internal use)
endpoints.shutdown.sensitive=false
management.context-path=/manage
management.port=8088
management.address=127.0.0.1
# With security
endpoints.shutdown.sensitive=true
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER</code>

The endpoint maps to ShutdownMvcEndpoint , which delegates to ShutdownEndpoint.invoke() . The implementation creates a new thread that calls AbstractApplicationContext.close() after a short delay, allowing the HTTP response to be returned before the JVM terminates.

Conclusion

By combining Linux signals, JVM signal handlers, Runtime shutdown hooks, and Spring Boot's actuator endpoint, developers can achieve a controlled and graceful shutdown of Java services, ensuring resources are released and data integrity is maintained.

javaSpring BootGraceful ShutdownSignal HandlingKill CommandRuntime Hook
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow 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.