How to Write a Java Application That Can Update Itself at Runtime

How can I write a Java application that can update itself at runtime?

The basic structure of a solution is as follows:

  • There is a main loop responsible for repeatedly loading the latest version of the app (if required) and launching it.

  • The application does its thing, but periodically checks the download URL. If it detects a new version it exits back to the launcher.

There are a number of ways you could implement this. For example:

  • The launcher could be a wrapper script or binary application that starts a new JVM to run the application from a JAR file that gets replaced.

  • The launcher could be a Java application that creates a classloader for the new JAR, loads an entrypoint class and calls some method on it. If you do it this way, you have to watch for classloader storage leaks, but that's not difficult. (You just need to make sure that no objects with classes loaded from the JAR are reachable after you relaunch.)

The advantages of the external wrapper approach are:

  • you only need one JAR,
  • you can replace the entire Java app,
  • any secondary threads created by the app, etc will go away without special shutdown logic, and
  • you can also deal with recovery from application crashes, etc.

The second approach requires two JARs, but has the following advantages:

  • the solution is pure Java and portable,
  • the changeover will be quicker, and
  • you can more easily retain state across the restart (modulo leakage issues).

The "best" way depends on your specific requirements.

It should also be noted that:

  • There are security risks with auto-updating. In general, if the server that provides the updates is compromised, or if the mechanisms for providing the updates are susceptible to attack, then auto-updating can lead to a compromise of the client(s).

  • Pushing a update to a client that cause damage to the client could have legal risks, and risks to your business' reputation.


If you can find a way to avoid reinventing the wheel, that would be good. See the other answers for suggestions.

How create a Java application that auto-updates itself?

You can use the URLClassLoader to load a jar file from an URL. Mind, URLs may also be local files, and I'd recommend that you download the jar file locally before loading it in, instead of loading it directly from the web. The reason for this is that you want the application to remain usable, even if the user cannot reach your server.

However, I'd also recommend against taking this approach. What if you want to update the launcher? It's better to download a seperate updater application from the main application, launch the seperate updater application, have that kill the main application and update it.

How to write a self-updating program in Java?

Java Web Start is meant specifically for this. You ship one jnlp file, and java takes care of fetching the newest version from a server.

Apart from that, you can download updated classes and replace them at runtime.

Updating an application(Java)

I would suggest that you have a look at JRebel. It can perform updates to classes during runtime. Here's a list of features (from their home page):

  • Changes to method bodies
  • Adding/removing Methods
  • Adding/removing constructors
  • Adding/removing fields
  • Adding/removing classes
  • Adding/removing annotations
  • Changing static field value
  • Adding/removing enum values
  • Changing interfaces
  • Replacing superclass

How to use URLClassloader in an auto-update jar launcher?

EDIT
I figured out one of the answers to one of the questions below. Turns out, I didn't need to do any of the code below. My main method loads a login screen but after it's loaded it returns back to the AppLauncher code, thus closing the URLClassLoader! Of course, at that point any requested class will not be found as the loader has been closed! What an oof! Hopefully I will save someone a headache in the future...

Original

Well, after more time, effort, research, and effective use of Eclipse's debugging tool, I was able to figure out what I needed to do to resolve my issues.

So the first issue was my JDBC driver was never registered when passing the jars to the URLClassloader. This is the part I sorta don't understand, so advisement would be welcomed, but there is a static block in the JDBC class that registers the driver so it can be used by DriverManager see code below. Loading the class is what executes that static block, hence why calling Class.forName works.

static {
try {
DriverManager.registerDriver(new JDBC());
} catch (SQLException e) {
e.printStackTrace();
}
}

What I don't understand, is how class loading works if jars are specified via the class path. The URLClassLoader doesn't load any of those classes until they are called, and I never directly work with the JDBC class, thus no suitable driver exception, but are all the classes specified via the classpath loaded initially? Seems that way for static blocks to execute.

Anyhow, to resolve my other issue with some of my app's classes not being found I had to implement my own classloader. I get what I did and how it works well, but still don't understand why I had to do it. All of my jars were loaded to the original URLClassloader so if I could find them and the files within, why couldn't it do it?

Basically, I had to override the findClass and findResource methods to return jarEntry information that I had to store. I hope this code helps someone!

public class SBURLClassLoader extends URLClassLoader {

private HashMap<String, Storage> map;

public SBURLClassLoader(URL[] urls) {
super(urls);
map = new HashMap<>();
try {
storeClasses(urls);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

private void storeClasses(URL[] urls) throws ClassNotFoundException {

for (URL u : urls) {
try {
JarFile jarFile = new JarFile(new File(u.getFile()));
Enumeration<JarEntry> e = jarFile.entries();

while (e.hasMoreElements()) {
JarEntry jar = e.nextElement();
String entryName = jar.getName();

if (jar.isDirectory()) continue;
if (!entryName.endsWith(".class")) {
//still need to store these non-class files as resources
//let code continue to store entry un-altered
} else {
entryName = entryName.replace(".class", "");
entryName = entryName.replace("/", ".");
}

map.put(entryName, new Storage(jarFile, jar));

System.out.println(entryName);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = null;
try {
c = super.findClass(name);
} catch (ClassNotFoundException e) {

Storage s = map.get(name);
try {
InputStream in = s.jf.getInputStream(s.je);
int len = in.available();
c = defineClass(name, in.readAllBytes(), 0, len);
resolveClass(c);
} catch (IOException e1) {
e1.printStackTrace();
}

if (c == null) throw e;
}
return c;
}

@Override
public URL findResource(String name) {
URL url = super.findResource(name);

if (url == null) {
Storage s = map.get(name);
if (s != null) {
try {
url = new URL("jar:"+s.base.toString() + "!/" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return url;
}

private class Storage {

public JarFile jf;
public JarEntry je;
public URL base;

public Storage(JarFile jf, JarEntry je) {
this.jf = jf;
this.je = je;
try {
base = Path.of(jf.getName()).toUri().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
}


Related Topics



Leave a reply



Submit