Leo Leo is a Java Geek and had the opportunity to apply his skills to various industry, from aerospace, e-commerce, and gaming. Leo is a DZone MVB and is not an employee of DZone and has posted 5 posts at DZone. You can read more from them at their website. View Full User Profile

Programmatically Restart a Java Application

07.06.2011
| 47149 views |
  • submit to reddit
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 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(runBeforeRestart != null) {
    runBeforeRestart.run();
}

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

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

/** 
 * Sun property pointing the main class and its arguments. 
 * Might not be defined on non Hotspot VM implementations.
 */
public static final String SUN_JAVA_COMMAND = "sun.java.command";

/**
 * 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
		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 (runBeforeRestart!= null) {
			runBeforeRestart.run();
		}
		// exit
		System.exit(0);
	} catch (Exception e) {
		// something went wrong
		throw new IOException("Error while trying to restart the application", e);
	}
}

 From : http://lewisleo.blogspot.jp/2012/08/programmatically-restart-java.html

Published at DZone with permission of Leo Lewis, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Andreas Haufler replied on Wed, 2011/07/06 - 6:25am

Nice article Leo,

however, one should be aware, that this uses undocumented APIs (like the property  "sun.java.command") and therefore will not work across all VMs. As long as you stay on the HotSpot VM and Oracle doesn't change too much, you're fine though...

Regarding your excellent code style and code/comment ratio, you might want make this a constant so it stands out a bit.Except that, it's really well researched and written.

 regards Andy

 

Leo Lewis replied on Wed, 2011/07/06 - 8:50am in response to: Andreas Haufler

Thank you Andreas,

Indeed, you are totally right, the  sun.java.command is the weak point of this method. I updated the article to set it constant in my code, and add some comments about it. It's a shame because it does exactly what I was looking for.

A workaround which would avoid to use it, would be to create a class RestartManager that would contain a singleton, our restartApplication() method and some method :

public void registerMain(Class<?> main, String[] args) {
...
}

that would be called in the main method of the application, to keep somewhere in memory the main class and the launch arguments. We lost the simplicity of a single static method that do the all trick, but we gain in portability.

Regards,

Leo

Anant Jagania replied on Wed, 2011/07/06 - 9:24am

Leo, Really good article.. Very interesting... Can the same be used to restart the application in a different JVM.... Like from tomcat web server, I try to start/restart different application but should be running outside tomcat !

Dmitry Zubanov replied on Wed, 2011/07/06 - 11:58am

Leo, it's worked excellent! I checked it in my app. But in your code
final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);
quotes are not needed.
final StringBuffer cmd = new StringBuffer(java + " " + vmArgsOneLine);
Very nice solution. I like it!

Leo Lewis replied on Wed, 2011/07/06 - 8:26pm in response to: Dmitry Zubanov

Thank you Dmirty,

In fact the quotes are probably not needed on most linux/unix installation, because the path to the java executable usually does not contain spaces " " characters (something like /usr/java/jdk.../bin/java).

But it is usually required on Windows where java is installed by default in C:\Program files\Java\jdk...\bin\java.

In this case, the command will failed if you miss the quotes.

Leo

 

Leo Lewis replied on Thu, 2011/07/07 - 3:26am in response to: Anant Jagania

Anant,

Interesting question you have here.

Yes, you can command a JVM1 to restart from another JVM2 (or another process, not necessarily a JVM), by sending some signal to the JVM1 which will process the signal and call the restartApplication method.

We can think of sending this reboot signal by socket, or HTTP etc.

As the launch of the new JVM is done in a shutdownHook, you should not have some trouble of port already used by some SocketServer.

I made this experience with my Tomcat, I command it to restart itself from my web browser by calling this simple servlet (forgive me the fact that usually the SecurityManager should not permit a System.exit() to be called by a servlet).

public class RestartTomcatServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String query = req.getParameter("query");
		if ("restart".equals(query)) {
			restartApplication(new Runnable() {
				@Override
				public void run() {
					System.out.println("Restarting the server ...");
				}
			});
		} else {
			resp.getWriter().println("Server uptime : " + (ManagementFactory.getRuntimeMXBean().getUptime() / 1000) + "s");
		}
	}
}

Start tomcat, wait a little bit, call my servlet with no argument :

Server uptime : 57s

Calling my servlet with ?query=restart argument :

Page is loading, refresh the page after a couple of seconds :

Server uptime : 8s

Leo

 

Dmitry Zubanov replied on Thu, 2011/07/07 - 5:00am in response to: Leo Lewis

Leo, you are absolutely right. I am testing your solution on Ubuntu. :)

Rui Vale replied on Tue, 2011/07/12 - 12:35pm

Leo,

really tx for the work you post here! Congrats ... 

 

RGV

Anant Jagania replied on Thu, 2011/07/14 - 5:10am in response to: Leo Lewis

Leo, This is amazing. Makes lot of sense. Working out with restarting different applications on *nix environment would definitely help build a web app which can control all the sub applications. Thanks a lot...

Carla Brian replied on Tue, 2012/06/19 - 5:49pm

Good job on this one. I didn't know muc about this one. Glad to see your post. - Instant Tax Solutions Scam

Nenurodomas Not replied on Thu, 2012/08/23 - 8:20am

Hi, Leo
I test the source and it give me an error on a method System.getProperty("sun.java.command") with the key "sun.java.command". It return null. While other keys eg. "java.class.path", "user.dir", "user.home" give me a right value. Any help?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.