Tuesday, August 14, 2012

Programmatically restart a Java application

Today I'll talk about a famous problem : restarting a Java application. It is especially useful when changing the language of a GUI application, so that we need to restart it to reload the internationalized messages in the new language. Some look and feel also require to relaunch the application to be properly applied.

A quick Google search give plenty answers using a simple :

Runtime.getRuntime().exec("java -jar myApp.jar");
System.exit(0);

This indeed basically works, but this answer that does not convince me for several reasons :
1) What about VM and program arguments ? (this one is secondary in fact, because can be solve it quite easily).
2) What if the main is not a jar (which is usually the case when launching from an IDE) ?
3) Most of all, what about the cleaning of the closing application ? For example if the application save some properties when closing, commit some stuffs etc.
4) We need to change the command line in the code source every time we change a parameter, the name of the jar, etc.

Overall, something that works fine for some test, sandbox use, but not a generic and elegant way in my humble opinion.

Ok, so my purpose here is to implement a method :

public static void restartApplication(Runnable runBeforeRestart) throws IOException {

...

}

that could be wrapped in some jar library, and could be called, without any code modification, by any Java program, and by solving the 4 points raised previously.

Let's start by looking at each point and find a way to answer them in an elegant way (let's say the most elegant way that I found).

1) How to get the program and VM arguments ? Pretty simple, by calling a :

ManagementFactory.getRuntimeMXBean().getInputArguments();

Concerning the program arguments, the Java property sun.java.command we'll give us both the main class (or jar) and the program arguments, and both will be useful.

String[] mainCommand = System.getProperty("sun.java.command").split(" ");

2) First retrieve the java bin executable given by the java.home property :

String java = System.getProperty("java.home") + "/bin/java";

The simple case is when the application is launched from a jar. The jar name is given by a mainCommand[0], and it is in the current path, so we just have to append the application parameters mainCommand[1..n] with a -jar to get the command to execute :

String cmd = java + vmArgsOneLine + "-jar " + new File(mainCommand[0]).getPath() + mainCommand[1..n];

We'll suppose here that the Manifest of the jar is well done, and we don't need to specify either the main nor the classpath.

Second case : when the application is launched from a class. In this case, we'll specify the class path and the main class :

String cmd = java + vmArgsOneLine + "-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0] + mainCommand[1..n];


3) Third point, cleaning the old application before launching the new one.
To do such a trick, we'll just execute the Runtime.getRuntime().exec(cmd) in a shutdown hook.
This way, we'll be sure that everything will be properly clean up before creating the new application instance.

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        ...
    }
});

Run the runBeforeRestart that contains some custom code that we want to be executed before restarting the application :

if(beforeRestart != null) {
    beforeRestart.run();
}

And finally, call the
System.exit(0);

And we're done. Here is our generic method :

/**
 * Restart the current Java application
 * @param runBeforeRestart some custom code to be run before restarting
 * @throws IOException
 */
public static void restartApplication(Runnable runBeforeRestart) throws IOException {
    try {
        // java binary
        String java = System.getProperty("java.home") + "/bin/java";
        // vm arguments
        List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
        StringBuffer vmArgsOneLine = new StringBuffer();
        for (String arg : vmArguments) {
            // if it's the agent argument : we ignore it otherwise the
            // address of the old application and the new one will be in conflict
            if (!arg.contains("-agentlib")) {
                vmArgsOneLine.append(arg);
                vmArgsOneLine.append(" ");
            }
        }
        // init the command to execute, add the vm args
        final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);
        // program main and program arguments (be careful a sun property. might not be supported by all JVM) 
        String[] mainCommand = System.getProperty("sun.java.command").split(" ");
        // program main is a jar
        if (mainCommand[0].endsWith(".jar")) {
            // if it's a jar, add -jar mainJar
            cmd.append("-jar " + new File(mainCommand[0]).getPath());
        } else {
            // else it's a .class, add the classpath and mainClass
            cmd.append("-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0]);
        }
        // finally add program arguments
        for (int i = 1; i < mainCommand.length; i++) {
            cmd.append(" ");
            cmd.append(mainCommand[i]);
        }
        // execute the command in a shutdown hook, to be sure that all the
        // resources have been disposed before restarting the application
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    Runtime.getRuntime().exec(cmd.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        // execute some custom code before restarting
        if (beforeRestart != null) {
            beforeRestart.run();
        }
        // exit
        System.exit(0);
    } catch (Exception e) {
        // something went wrong
        throw new IOException("Error while trying to restart the application", e);
    }
}

12 comments:

  1. Thanks a lot! Just came across this, works like charm!

    ReplyDelete
  2. nice tutorial... how do I call this application from a class please.

    ReplyDelete
  3. very nicely explained tutorial. But how to invoke this application from my class?

    ReplyDelete
  4. Can anybody please reply to above request??

    ReplyDelete
  5. Wow, I guess I came in the perfect moment haha, JK.

    Just you have to treat like a simple java method, I mean, just put it into a java class and wherever you are just make a instance of that class and call the method (restartApplication()). In the parameter you can write null if you don't have any code that you want to execute before the restarting of the application.
    It works for me perfectly.

    Regards :D

    ReplyDelete
    Replies
    1. Thanks!! it worked i just passed null in the parameters after creating an instance and surrounding with try catch and it ran.

      Delete
  6. Attend The Python Training in Bangalore From ExcelR. Practical Python Training in Bangalore Sessions With Assured Placement Support From Experienced Faculty. ExcelR Offers The Python Training in Bangalore.

    ReplyDelete
  7. wow, It is really helpful to me.I am really impressed with the way of writing of this blog. Thanks for sharing this good blog with good updates!!

    Machine Learning Training in Bangalore

    ReplyDelete
  8. Attend The Data Analytics Courses in Bangalore From ExcelR. Practical Data Analytics Courses in Bangalore Sessions With Assured Placement Support From Experienced Faculty. ExcelR Offers The Data Analytics Courses in Bangalore.
    ExcelR Data Analytics Courses in Bangalore

    ReplyDelete
  9. Not working when we are usung web start jnlp file.

    Please suggest if have any idea .

    Thanks

    ReplyDelete
    Replies
    1. You can tak a look how we restart JOSM, it's working with WebStart too: https://josm.openstreetmap.de/browser/trunk/src/org/openstreetmap/josm/actions/RestartAction.java

      Delete