While I’ve worked on a number of Spring Boot applications, I never had to personally write graceful shutdown code - it was either absent or was already in place. This post, a reference for my future self, captures what I learned and implemented to shutdown a Spring Boot application gracefully.

Enable Graceful Shutdown

This technique is best for HTTP request draining.

# application.yml
server:
  shutdown: graceful

spring:
  lifecycle:
    # Wait up to 30s for active requests to complete
    timeout-per-shutdown-phase: 30s

Custom Shutdown Logic

This technique is best for cleaning up beans.

@Service
public class MyService {
    @PreDestroy
    public void onShutdown() {
        // You custom cleanup logic: close connections etc.
    }
}

Implement @DisposableBean

This technique is an alternative but similar to the previous one for automatic bean cleanup.

@Component
public class DbConnectionPoolManager implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        // Cleanup: Release DB connections, close pools, etc.
    }
}

Listening to Context Events

This technique is best for app-wide shutdown cleanup logic.

@Component
public class ShutdownEventListener {
    @EventListener(ContextClosedEvent.class)
    public void onContextClosed(ContextClosedEvent event) {
        // Custom cleanup
    }

    @EventListener(ApplicationReadyEvent.class)
    public void onReady(ApplicationReadyEvent event) {
        System.out.println("Application is ready");
    }
}

Graceful Shutdown for Scheduled Tasks

This technique is specific to scheduled tasks.

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        final var scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(30);
        scheduler.initialize();
        registrar.setTaskScheduler(scheduler);
    }
}

Conclusion

I have listed only the techniques that were relevant to me at the time of this writing. It is possible that there are other techniques, especially for targeted cases. Let me know if there are.

Hero image credit: Basic Apple Guy