Search code examples
javaspring-boottomcat

How do I properly restart a Spring boot application?


I'm using the code from here to restart my Spring Boot application:

Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();

However, the restarted application fails to create beans, ultimately due to a RejectedExecutionException thrown when trying to schedule @Scheduled-annotated methods:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myBean' defined in URL [insert-path-here]: Initializetion of bean failed; nested exception is org.springframework.core.TaskRejectedException: Executor [java.util.concurrent.ScheduledThreadPoolExecutor@284a4a89[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 21508]] did not accept task: my.package.MyBean.scheduledMethod

The logs immediately after the doRestart() invocation indicate that the ExecutorService taskScheduleris being shut down, which would explain why this is happening. What I'm not entirely sure of is why the ExecutorService isn't recreated. Embarrassingly enough, it turns out that taskScheduler was a bean defined by my application and was not annotated @RefreshScope. Now the error I'm getting is

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'anotherBean' definen in URL[...]: Unsatisfied dependency expressed through constructor parameter 0 ... nested exception is  java.lang.IllegalStateException: org.springframework.boot.servlet.context.AnnotationConfigServletWebServerApplicationContext@2a95076c has been closed already

Scrolling up a bit in the logs reveals that there's an ApplicationContextException thrown with the stacktrace containing doRestart:

org.springframework.ApplicationContextException: Unable to start web server; nested exception is java.lang.IllegalStateException: Filters cannot be added to context [/myapp] as the context has been initialized
    at org.springframework.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:157)
    ...
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
    at org.springframework.cloud.context.restart.RestartEndpoint(RestartEndpoint.java:160)
    at my.package.MyBean.lambda$doThingAndRestart$2 (MyBean.java: 118)

Googling this error seems to result in a single page that isn't relevant, although I do note that this application is running on tomcat as well.

I suspect that this is related to the application creating a wrong sort of application context, which is a AnnotationConfigServletWebServerApplicationContext, which doesn't extend AbstractRefreshableAplicationContext.

What do I need to do to get rid of this error?

Please bear with me when I only copy single lines from the stack trace. I'm on a two-computer set-up, so I can't copy-and paste and am no allowed to otherwise move the logs to a computer connected to the Internet.


Solution

  • It turns out that RestartEndpoint only works with Spring's internal Tomcat, not an external one. So, another solution is required. The ones I discovered are

    • Using Tomcat's WatchedResource to re-deploy the server when a configuration file is overwritten. This has the downside of needing to maintain the Tomcat configuration when maintaining the doThingAndRestart functionality.
    • Using Tomcat's Manager App. I noticed that the reload request blocks until the app is done reloading, which overflowed the heap space.
    • Using Spring's RefreshEndpoint. For whatever reason, this didn't refresh the datasources.