Automatically Update your Javapackager Applications

For years we shipped our desktop applications, ReportMill and SnapCode, as Java Web Start applications, enjoying the built-in auto-update feature it provided. This year we began shipping our apps as native app packages using Javapackager, which makes it easy to generate a exe/app for Windows/MacOS. This has been a huge improvement – we no longer worry what JRE is installed, whether it is broken/missing or whether it will change. The JRE is entirely under our control and hidden from the user. The fact that we use Java at all is now just an “implementation detail”.

The only concern we had when switching was that we still want users to automatically get the latest update without having to re-install the latest version. Fortunately, we found a simple solution: We put the core of the app in a single compact jar file (.pack.gz – like Web Start) on our website and we have an AppLoader main class that does the following:

  1. Check for new version of jar file on web site
  2. If found, download and alert user “New Update Available on Relaunch”
  3. If update from above step is in App-Support directory, move into place
  4. Create URLClassLoader for latest jar
  5. Get main class from URLClassLoader, and invoke real app main()

We were able to do this in a single AppLoader.java file in 250 lines of code that only uses JRE classes. To use this in your application, add AppLoader.java to your project, make it the main class and customize the string constants at the top of the file for your AppName, JarName, JarURL and MainClass.

Thanks to Jim Weaver for inspiring us to share this. Hopefully this works well for you, too!

Source code:  AppLoader.java

Advertisements

22 Responses to “Automatically Update your Javapackager Applications”

  1. JavaFX links of the week, December 8 // JavaFX News, Demos and Insight // FX Experience Says:

    […] SnapCode blog has a post on how to automatically update your JavaFX applications by running a small bootstrap program before your main […]

  2. Java desktop links of the week, December 8 « Jonathan Giles Says:

    […] SnapCode blog has a post on how to automatically update your JavaFX applications by running a small bootstrap program before your main […]

  3. shadzic Says:

    First thank you for the code! It’s very helpful since you’re not the same with that problem.

    Few questions I have :
    – What is the point of unpacking? Is it because you have many folders? In my case I only have one big jar, so I could squeeze that part and directly write bytes in the the new .update right?

    -Could we possibly wait for the newly updated package to be ready before launching? This will avoid to re-start in order to have the new update right? Thus, if the new package is quite huge, this could take some time I agree.

    -Could we run into permission issues when copying from source dir to local dir somehow?

    Best regards

  4. reportmill Says:

    Re: Unpacking: That’s a good point – I assumed that URLClassLoader needed a .jar URL instead of the .jar.pack.gz, but perhaps that isn’t the case.

    Re: Wait for update: You could certainly do this – with a progress bar and everything. In truth, I went this way because it was easier, less prone to trouble and doesn’t add to launch time. I also think it is less disruptive – what if the download takes 10 minutes and the user just wanted to get something done quickly. I’ve seen this “Relaunch to install Update” strategy a lot lately and I personally like it better than waiting for updates.

    Re: Permissions: I don’t think there should be a permissions problem (I haven’t seen one yet). Your .exe/.app should have full access to the user account. Perhaps there could be a backup strategy to use the original jar in-place if write fails to the app-support directory.

  5. shadzic Says:

    I’ve investigated a bit more the code and tried to reproduce but it fails..

    In fact I don’t understand the “copyDefaultMainJar” method. You have one installation (basically in Appsupport because it’s the default ). And you try to copy that installation to … AppSupport. So you will duplicate and have in the end two installations? I’m having trouble understanding the fact of copying the exact same jar to another place..

    I tried on my side but since I have my application bundled in the beginning, The classLoader is not working very well.. I update the jar and tell him to load the specific updated jar but it seems to load the original bundled jar..

  6. reportmill Says:

    The code copies the original jar from the exe/app bundle to the App-Support directory if there is no jar there yet (so there is a default jar). I suppose this is unnecessary – it should probably just launch normally if there is no download jar (no URLClassLoader needed).

  7. Graham Bryan Says:

    So I have a weird one, would appreciate your thoughts. The whole process seems to be working well but when the app executes the updated jar the output is telling me that it is still using the original variable values. The only thing I can think of is that the native launch loads the bundles in so when it attempts to load the updated bundle it doesn’t load the classes already in memory but tbh this a bit of a guess.

  8. reportmill Says:

    Perhaps when AppLoader creates its URLClassLoader, it should explicitly specify the bootstrap ClassLoader. I think it uses the SystemClassLoader by default. The only way I know to get the bootstrap ClassLoader is with ClassLoader.getSystemClassLoader().getParent().

  9. Terje Dahl Says:

    How would you integrate this custom class loader into a native JavaFX app?
    First, do you package this class in a separate JAR than your main application code?
    Then, how does the startup-call know to go from this, and into the main applications start?
    And finally, this also means that this class loader itself isn’t updatable together with the rest of the app?

    • reportmill Says:

      I do actually package this class as a separate jar, although at some point I think I would like to change it to where it is in the same jar. I don’t think this would be a problem, although, I might need to create the URLClassLoader initialized with the other jar URLs from the SystemClassLoader, then set it’s parent to the BootstrapClassLoader.

      And you’re right – with this I can’t dynamically update the AppLoader class or the JRE. That is acceptable to me however.

  10. Terje Dahl Says:

    … and a slightly higher-level question:
    Your native installer is presumably signed, which ensures that also your original (initial run) JAR is unmodified.
    Any thoughts on the danger of not verifying the integrity of the downloaded update as it is downloaded over a non-SSL connection? An altered JAR could be inserted along the way, and the user would then unwittingly run possibly malicious code on restart …

    • reportmill Says:

      I suppose I could check the certificate of the new jar against the certificate of the original jar. I find it hard to see how this could be done unless someone was updating on a insecure machine or network. I’ll have to look into this for a future release. 🙂

      • Terje Dahl Says:

        Yes, at a school, for example, I would want to ensure the integrity of the package between server and machine. You can not always be sure what some crafty teenager might get up to (set up fake hotspot, or somehow insert something into a proxy on a badly controlled school network). I would hate to be the one who accidentally left a back door open on pupil’s machines.
        Good point about checking signature against certificate of original jar. I will look into that, and perhaps post/send something back to you if I come up with something.

      • Terje Dahl Says:

        I have now examined the security asspects of loading and running a jar.
        As I mentioned, the danger consists of a “man-in-the-middle-attack”, where someone might intercept your update-jar and replace it with his own version.
        His own version would either: Replace your entry-point class with his own “main”, or he might alter the manifests Main-Class to point to his main-class, which could run his code, and then call your original main. The user would never know!
        This is easy to do. I tried it successfully.
        However, if a jar-file is signed, it is pretty much “tamper-proof”: You cannot change the manifest.
        And when you load the class with URLClassLoader, the classloader will automatically verify the jar if it is signed.
        The alternative for the hacker then is to either rebuild your jar with his code and not sign it, or simply re-sign it himself using his own certificate (self-signed cert should be do the trick).

        So then what you _do_ have to check is: 1. _Is_ the jar signed? and 2. _Who_ has signed it?
        I have put together this a simple function that does this. It is optimized for Java8 and it is made only for my certificate.
        (You may have to tweek it a little to make it work for your specific certificate-chain.)

        Use this code freely. I would appreciate attributions.

        Please excuse the “lisp-y” parens-format. I like it.
        This is the only part of my program that is written in Java, not Clojure.

        ““
        static boolean verifyJarSigner(File jar) {
        String signer = “terje@andante.no”;
        System.out.println(“verifyJarSigner() jar: “+jar+” , signer: “+signer);
        try {
        JarFile jf = new JarFile(jar, true);
        JarEntry entry = jf.getJarEntry(“META-INF/MANIFEST.MF”);
        getBytes(jf.getInputStream(entry)); // must read entire entry-stream to get certificates at end!
        java.security.cert.Certificate [] certs = entry.getCertificates();
        jf.close(); // must close jar-file to be able to delete it later if necessary!
        if(certs != null) {
        for (Certificate cert : certs) {
        Collection<List> sans = ((X509Certificate)cert).getSubjectAlternativeNames();
        if(sans != null) {
        for (List san : sans) {
        if (signer.equals(((List) san).get(1)))
        return true; }}}}}
        catch (IOException | CertificateParsingException e) {
        e.printStackTrace(); }
        System.out.println(” Reached end. No certs found or no matching signer found in certs.”);
        return false; }

        ““

        Call it with:
        ““
        verifyJarSigner(updateUnpacked);
        ““

        Make sure that the jar is properly deleted if it doesn’t verify! (Otherwise it would be loaded on restart anyways.)
        You might want to do this:
        ““
        Path path = updateUnpacked.toPath();
        try {
        Files.delete(path);
        System.out.println(” Deleting successfully.”); }
        catch (NoSuchFileException x) {
        System.err.format(“%s: no such” + ” file or directory%n”, path);}
        catch (DirectoryNotEmptyException x) {
        System.err.format(“%s not empty%n”, path);}
        catch (IOException ioe) {
        // File permission problems are caught here.
        ioe.printStackTrace();}}
        ““
        Finally, you might want to put a Thread.sleep(3000) at the beginning of checkForUpdates, to prevent the programming finding and loading the new jar immediately on slow system!

  11. juliettekaro Says:

    Hi, this looks really good however I can’t seem to make it work with my program, when I execute my main it works fine but when I launch it with the AppLoader, it throws an Invocation Target Exception when loading an image

    The code where the error occures:
    for(int i=0;i1434532001000)
    java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at model.AppLoader.main1(AppLoader.java:68)
    at model.AppLoader.main(AppLoader.java:29)
    Caused by: java.lang.RuntimeException: Internal graphics not initialized yet
    at com.sun.glass.ui.Screen.getScreens(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.getScreens(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.getMaxPixelScale(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.loadImage(Unknown Source)
    at javafx.scene.image.Image.loadImage(Unknown Source)
    at javafx.scene.image.Image.initialize(Unknown Source)
    at javafx.scene.image.Image.(Unknown Source)
    at model.SaveFiles.loadChamp(SaveFiles.java:156)
    at model.SaveFiles.loadall(SaveFiles.java:97)
    at model.Main.Init(Main.java:21)
    at model.Main.main(Main.java:26)
    … 6 more

    I don’t really know why this happens as the class shouldn’t affect loading images I think (the images are saved in the folder next to the jar and text that is loaded before the image works fine)

    Could it maybe be a problem that I use JavaFX and not swing? Are images managed the same there?

    Well, it would be great if someone could help me

    regards,
    Karoline

  12. juliettekaro Says:

    In my last comment this part seems to have been cut out:

    The code where the error occures:
    for(int i=0;i<NrofChampions;i++){
    System.out.println(i);
    //read icons
    File file=new File(savePic+i+"/icon.png");
    URL url = file.toURI().toURL();
    Image image = new Image(url.toExternalForm());
    //…
    }

    Console output when launching main are as expected numbers from 0 to NrofChampions but with appLoader I get this:

    • reportmill Says:

      To prevent the JavaFX exception (Internal Graphics not initialized yet) – I think you just need to call new JFXPanel(). Even though you may not need it, creating it does the job of initializing JavaFX.

    • Graham Bryan Says:

      I had this issue loading any asset from the bundle you are updating. I believe the only solution is to use the ClassLoader.getResourceAsStream functionality.

  13. Dwayne Patel Says:

    This is exactly what I am looking for. Can anybody explain how it works?
    I have my application bundled into one jar file (database and other jars). If I add the AppLoader class to my main method then It checks with the server but doesn’t do anything even if it finds an update. If I package the AppLoader into its own jar how do I link them?

    • reportmill Says:

      Sounds like you have two problems. The first, “doesn’t do anything even if it finds an update” is surprising to me. Are you saying the checkForUpdates thread just dies/hangs if it finds an update? You should be able to debug or println to get more info.

      Regarding class linking: I do jar AppLoader in it’s own jar and include it in the package (using javapackager). Having your app use multiple jars is just a matter of getting the classpath right.

  14. Dwayne Patel Says:

    One problem I am having is when I run the AppLoader (through netbeans) I was getting filenotfoundexception access is denied. I changed
    File jar1 = new File(path0); to File jar1 = new File((path0) + “SnapCode1.jar.pack.gz”);
    To solve that and when I ran the program again I was getting classnotfoundexception: snap.app.App.
    If I force the program to update the jar.pack.gz from the server then the programs runs but only one time. If I restart then I am back to classnotfoundexception: snap.app.App
    I have noticed that there is a difference in file size of the jar in the C:\Users\home\AppData\Local\SnapCode folder. When the program is run first then the jar is 1kb and cannot be opened. When I force an update the file is 2kb and can be accessed.

  15. David Says:

    Love it! Thanks for this pattern. I have implemented it for our EMR in place of WebStart.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: