javajarauto-updatefile-access

Issue while using `new File(".")` when executing a new jar programmatically


MY SITUATION:

Using Java 17.0.8

I have an app A.jar that on startup checks for updates, downloads the update and then runs B.jar to replace the app files with the freshly downloaded ones.

THE PROBLEM:

From A, when I run the B app and then exit(0), the A app should release the lock on the A.jar file after the JVM closes, so the B app can then replace the files with the new updated version. Instead, the A.jar file remains locked and the B app can't overwrite the A files with the update.

Instead, if the A app exits, and then I manually launch the B app, it works as espected. I've also tried to run the B app launcingh it with the cmd /c nohup but it still doesn't release the lock on A files...

DEBUG:

making a fresh project and compiling this code three times, once for A.jar, once for B.jar and the last one for A2.jar, then running the A.jar, then it updates to A2.jar version successfully as expected.

public class Main implements Runnable {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Main());
    }

    @Override
    public void run() {
        String x = "A"; //"A" for A.jar, "B" for B.jar and "A2" for A2.jar

        File dir = new File(".").getAbsoluteFile().getParentFile();
        File A = new File(dir.getAbsolutePath() + File.separator + "A.jar");
        File B = new File(dir.getAbsolutePath() + File.separator + "B.jar");
        File A2 = new File(dir.getAbsolutePath() + File.separator + "A2.jar");
        switch (x) {
            case "A" -> {
                JOptionPane.showMessageDialog(null, x + ": Lancio Aggiornamento!");
                try {
                    Runtime.getRuntime().exec("cmd /c java -jar \"" + B.getAbsolutePath() + "\"");
                    System.exit(0);
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, x + ": Aggiornamento Fallito!");
                }
            }
            case "B" -> {
                try {
                    JOptionPane.showMessageDialog(null, x + ": Sto Aggiornando!");
                    Files.copy(A2.toPath(), A.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    A2.delete();
                    Runtime.getRuntime().exec("cmd /c java -jar \"" + A.getAbsolutePath() + "\"");
                    System.exit(0);
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, x + ": Aggiornamento Fallito!");
                }
            }
            case "A2" -> {
                B.delete();
                JOptionPane.showMessageDialog(null, x + ": Sono Aggiornamento!");
            }
            default -> { }
        }
    }

}

So I imagine it's an issue with my project. The problem is that there is too much code and libraries to trying and find the culprit...

THE QUESTION:

What could be keeping the main .jar file loked after I exit the JVM and it fully closes, even disappearing from task manager? Shouldn't it release any remaining lock on the .jar files?

I know if I don't close a file stream properly then it might not fully release the lock, but I'm not directly doing anything to the jar file itself...


Solution

  • OK, this was a weird one...

    I cloned the app project and started to remove one bit at the time untill I was left with this:

    public class Main implements Runnable {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Main());
        }
        @Override
        public void run() {
            String dir = new File(".").getAbsoluteFile().getParentFile().getAbsolutePath() + File.separator;
            try {
                Runtime.getRuntime().exec("cmd /c java -jar \"" + dir + "Update" + File.separator + "Updater.jar\"");
            } catch (IOException ex) { }
            System.exit(0);
        }
    }
    

    at this point there was not a shadow of a doubt that it wasn't the app the problem, but the updater itself... so I proceeded to made a clone of that project as well and started to reverse-engineer it until I finally figured out what the problem was.

    This is what happened:

    my basic file structure is as follows:

    App Folder
    ├─ App.jar
    ├─ Update
    │  ├─ Updater.jar
    │  ├─ Data
    │  │  ├─ NewApp.jar
    

    App.jar launches Updater.jar and exites.

    The Updater.jar tries to figure out it's own location path to then be able to find the path to the parent App Folder and the path to the child Data folder.

    To find the .jar current directory I obtain it by doing new File(".").getAbsoluteFile().getParentFile();. this will make a '.' File object in the same directory as the .jar file of the running application. By gettin this object's parent, I basically have the directory path to the current Application. This is what breaks!

    If I open the Updater.jar manually, the path is correct and there is no issue.

    But if it's the App.jar that opens the Updater.jar, then for some reason the new File(".") called by the Updater app will no longer be created in the Updater.jar directory, but in the App.jar directory where the Updater was executed from.

    To fix it I momentarily replaced the faulty code with new File(context.getProtectionDomain().getCodeSource().getLocation().toURI()) to get the current App Directory and it's working alright, but I'll have to find a "proper" way and really look into it better, as I'm starting to grow suspicious of this solution as well!

    This to me makes no sense to be honest, and I feels like it's more of a bug rather than a feature... but none the less, I finally got to the bottom of it and it's now all working as expected!