I am trying to programmatically restart my Spring Application without having the user to intervene.
Basically, I have a page which allows to switch the mode of the a
You can use the RestartEndPoint (in spring-cloud-context dependency) to restart the Spring Boot application programmatically:
@Autowired
private RestartEndpoint restartEndpoint;
...
Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();
It works, even though it will throw an exception to inform you that this may lead to memory leaks:
The web application [xyx] appears to have started a thread named [Thread-6] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
The same answer was provided for this other question (worded differently): Call Spring actuator /restart endpoint from Spring boot using a java function
Even though Alex's solution works, I don't believe in including 2 additional dependencies (Actuator and Cloud Context) just to be able to do one operation. Instead, I have combined his answer and modified my code in order to do what I wanted.
So, first of all, it is crucial that the code is executed using new Thread() and setDaemon(false);. I have the following endpoint method that handles the restart:
val restartThread = Thread {
logger.info("Restarting...")
Thread.sleep(1000)
SpringMain.restartToMode(AppMode.valueOf(change.newMode.toUpperCase()))
logger.info("Restarting... Done.")
}
restartThread.isDaemon = false
restartThread.start()
The Thread.sleep(1000) is not required, but I want my controller to output the view before actually restarting the application.
SpringMain.restartToMode has the following:
@Synchronized fun restartToMode(mode: AppMode) {
requireNotNull(context)
requireNotNull(application)
// internal logic to potentially produce a new arguments array
// close previous context
context.close()
// and build new one using the new mode
val builder = SpringApplicationBuilder(SpringMain::class.java)
application = builder.application()
context = builder.build().run(*argsArray)
}
Where context and application come from the main method upon starting the application:
val args = ArrayList<String>()
lateinit var context: ConfigurableApplicationContext
lateinit var application: SpringApplication
@Throws(Exception::class)
@JvmStatic fun main(args: Array<String>) {
this.args += args
val builder = SpringApplicationBuilder(SpringMain::class.java)
application = builder.application()
context = builder.build().run(*args)
}
I am not entirely sure if this produces any problems. If there will be, I will update this answer. Hopefully this will be of any help to others.
Below restart method will work.
`@SpringBootApplication public class Application {
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = SpringApplication.run(Application.class, args);
}
public static void restart() {
ApplicationArguments args = context.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
context.close();
context = SpringApplication.run(Application.class, args.getSourceArgs());
});
thread.setDaemon(false);
thread.start();
}
}`
I've solved this issue by using Restarter from Spring Devtools. Add this to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
Then use org.springframework.boot.devtools.restart.Restarter to call this:
Restarter.getInstance().restart();
It works for me. Hope this help.
As was commented already, the restart-via-thread implementations given before only work once, and second time around throw a NPE because context is null.
This NPE can be avoided by having the restart thread use the same class loader as the initial main-invoking thread:
private static volatile ConfigurableApplicationContext context;
private static ClassLoader mainThreadClassLoader;
public static void main(String[] args) {
mainThreadClassLoader = Thread.currentThread().getContextClassLoader();
context = SpringApplication.run(Application.class, args);
}
public static void restart() {
ApplicationArguments args = context.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
context.close();
context = SpringApplication.run(Application.class, args.getSourceArgs());
});
thread.setContextClassLoader(mainThreadClassLoader);
thread.setDaemon(false);
thread.start();
}
In case it might help someone, here's a pura Java translation of Crembo's accepted answer.
Controller method:
@GetMapping("/restart")
void restart() {
Thread restartThread = new Thread(() -> {
try {
Thread.sleep(1000);
Main.restart();
} catch (InterruptedException ignored) {
}
});
restartThread.setDaemon(false);
restartThread.start();
}
Main class (significant bits only):
private static String[] args;
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
Main.args = args;
Main.context = SpringApplication.run(Main.class, args);
}
public static void restart() {
// close previous context
context.close();
// and build new one
Main.context = SpringApplication.run(Main.class, args);
}