66

How can I register/add a custom shutdown routine that shall fire when my Spring Boot application shuts down?

Scenario: I deploy my Spring Boot application to a Jetty servlet container (i.e., no embedded Jetty). My application uses Logback for logging, and I want to change logging levels during runtime using Logback's MBean JMX configurator. Its documentation states that to avoid memory leaks, on shutdown a specific LoggerContext shutdown method has to be called.

What are good ways to listen on Spring Boot shutdown events?

I have tried:

public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext cac = SpringApplication.run(Example.class, args);

    cac.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            logger.info("Do something");
        }
    });
}

but this registered listener does not get called when the application shuts down.

Thomas Traude
  • 840
  • 7
  • 17
Abdull
  • 26,371
  • 26
  • 130
  • 172
  • 3
    You are registering the listener after the context is created, basically rendering it useless. If you want it to participate you need to register it as a bean in your application context like any other bean. – M. Deinum Nov 02 '14 at 17:07
  • Does this answer your question? [Does spring have a shutdown process to put cleanup code?](https://stackoverflow.com/questions/8585350/does-spring-have-a-shutdown-process-to-put-cleanup-code) – Jason Law Oct 14 '20 at 06:40

5 Answers5

42

https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#features.spring-application.application-exit

Each SpringApplication will register a shutdown hook with the JVM to ensure that the ApplicationContext is closed gracefully on exit. All the standard Spring lifecycle callbacks (such as the DisposableBean interface, or the @PreDestroy annotation) can be used.

In addition, beans may implement the org.springframework.boot.ExitCodeGenerator interface if they wish to return a specific exit code when the application ends.

Blacklight
  • 3,809
  • 2
  • 33
  • 39
cfrick
  • 35,203
  • 6
  • 56
  • 68
  • I have updated my question to show my current unsuccessful effort – Abdull Oct 31 '14 at 16:24
  • 2
    Updated link https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-exit – Kenny Dec 18 '18 at 07:32
  • 1
    Exact tab link : https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-exit – Jabongg Sep 30 '21 at 09:26
34

have you tried this as mentioned by @cfrick ?

@SpringBootApplication
@Slf4j
public class SpringBootShutdownHookApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootShutdownHookApplication.class, args);
  }

  @PreDestroy
  public void onExit() {
    log.info("###STOPing###");
    try {
      Thread.sleep(5 * 1000);
    } catch (InterruptedException e) {
      log.error("", e);;
    }
    log.info("###STOP FROM THE LIFECYCLE###");
  }
}
lizhipeng
  • 661
  • 7
  • 6
10

Your listener is registered too late (that line will never be reached until the context has already closed). It should suffice to make it a @Bean.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
5

I have a similar use case, where I have to hold the server's shutdown process for some minutes, I have used the same approach mentioned in the question, the only change is instead of adding the listener after booting the service, I have added the listener (ContextClosedEvent) before running the application

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.addListeners((ApplicationListener<ContextClosedEvent>) event -> {
            log.info("Shutdown process initiated...");
            try {
                Thread.sleep(TimeUnit.MINUTES.toMillis(5));
            } catch (InterruptedException e) {
                log.error("Exception is thrown during the ContextClosedEvent", e);
            }
            log.info("Graceful Shutdown is processed successfully");
        });
        application.run(args);
    }
}
zb226
  • 9,586
  • 6
  • 49
  • 79
Prasanth Rajendran
  • 4,570
  • 2
  • 42
  • 59
4
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @NotNull
    @Bean
    ServletListenerRegistrationBean<ServletContextListener> myServletListener() {
        ServletListenerRegistrationBean<ServletContextListener> srb =
                new ServletListenerRegistrationBean<>();
        srb.setListener(new ExampleServletContextListener());
        return srb;
    }
}

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ExampleServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(
        ServletContextEvent sce) {
    // Context Initialised
    }

    @Override
    public void contextDestroyed(
        ServletContextEvent sce) {
       // Here - what you want to do that context shutdown    
   }
}
zb226
  • 9,586
  • 6
  • 49
  • 79
harsh tibrewal
  • 785
  • 6
  • 21