javaspringspring-mvcspring-bootkotlin

Programmatically restart Spring Boot application / Refresh Spring Context


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 application (actually meaning switching the currently active profile) and as far as I understand I must restart the context.

Currently my code is very simple, it's just for the restarting bit (this is Kotlin by the way):

    context.close()
    application.setEnvironment(context.environment)
    ClassUtils.overrideThreadContextClassLoader(application.javaClass.classLoader)
    context = application.run(*argsArray)

However the moment I do context.close() the JVM exists immediately. I have also tried context.refresh() but that seems to simply kill Tomcat/Jetty (tried both just in case it was a Tomcat problem) and then nothing happens.

I have also seen Programmatically restart Spring Boot application but nothing seems to work for me from those answers. Furthermore, I looked into Spring Actuator which supposedly has the /restart endpoint, but that doesn't seem to be there anymore?


Solution

  • 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.