0

In my JavaFX application, I want to show an error dialog and exit the app whenever some unexpected exception occurs. So in my main-method I have set up a default uncaught exception handler before launching the app:

setDefaultUncaughtExceptionHandler((thread, cause) -> {
    try {
        cause.printStackTrace();
        final Runnable showDialog = () -> {
           // create dialog and show
        };
        if (Platform.isFxApplicationThread()) {
           showDialog.run();
        } else {
           runAndWait(showDialog);
        }
    } catch (Throwable t) {
        // ???
    } finally {
        System.exit(-1);
    }
});

launch(MyApp.class);

Explanation: When the uncaught exception handler is executed on the JavaFX Application Thread (FXAT), I just run the code for showing the dialog. This of course doesn't work when the exception handler is not invoked by the FXAT. In this case, I have to push the code onto the FXAT. But I can't use Platform.runLater because then my app would exit before the dialog is shown. So, I made that custom method runAndWait which internally pushes the runnable via Platform.runLater, but waits until the execution of the runnable (with some countdown latch mechanism).

Now the problem with this: When an exception occurs in my start() method then my app gets stuck. Because it tries to wait until the execution of the dialog showing, but the FXAT never does this execution. I guess this is because when the start() method fails with an exception, the FXAT is just dead? I'm not sure whether this is a special case for the start() method or whether this will happen in any situation when an exception is thrown and not caught within code that is executed by the FXAT.

In Swing as I know the EDT is a complex architecture consisting of several threads. It wasn't the case that when some execution on the EDT failed that the entire Swing broke down. But here this is what seems to happen?

So what can I do here? How can I show to the user that the application cannot start?

user3237736
  • 845
  • 1
  • 9
  • 24
  • "I can't use `Platform.runLater()` because then my app would exit before the dialog is shown". Does calling `Platform.setImplicitExit(false);` fix this? – James_D Mar 12 '16 at 13:22
  • no of course not. the reason why the app exits is the System.exit() call in my exception handler. – user3237736 Mar 12 '16 at 14:52
  • wait, I'm an idiot. forget that point with the program exiting too early. because I could just move the System.exit call in the Runnable for the FX thread as well. However, there's stil the main problem that I cannot run anything on the FX thread when the start method throws an exception. – user3237736 Mar 12 '16 at 14:57
  • I was assuming `runAndWait()` blocked until the runnable had completed, which would prevent `System.exit(...)` from being called. Can you create a [MCVE] that illustrates the FX Platform from being closed due to an exception in the `start()` method? – James_D Mar 12 '16 at 15:03
  • 1
    Just experimenting and looking at jvisualvm, it looks like the FX Application Thread exits when there's an exception in the start method. I don't see a way around that... – James_D Mar 12 '16 at 15:49

1 Answers1

1

Well....

I have a solution but I don't particularly recommend it. By default, Application.launch() will catch the exception thrown by the start method, exit the FX Platform, and then rethrow the exception. Since the FX Application Thread has shut down when your default uncaught exception handler executes, waiting for something to happen on the FX Application Thread just blocks indefinitely.

The exception to this is when the FX Application is running in web start. The way that the launcher checks for this is to check for the presence of a security manager. So a (really, really ugly) workaround is to install a security manager so that it looks like you are running in web start mode. This line will install a permissive security manager:

System.setSecurityManager(new SecurityManager(){
    @Override
    public void checkPermission(Permission perm) {}
});

Here's a SSCCE:

import java.lang.Thread.UncaughtExceptionHandler;
import java.security.Permission;
import java.util.concurrent.FutureTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

public class ShowDialogOnException  {

    public static final UncaughtExceptionHandler ALERT_EXCEPTION_HANDLER = (thread, cause) -> {
        try {
            cause.printStackTrace();
            final Runnable showDialog = () -> {
               Alert alert = new Alert(AlertType.ERROR);
               alert.setContentText("An unknown error occurred");
               alert.showAndWait();
            };
            if (Platform.isFxApplicationThread()) {
               showDialog.run();
            } else {
               FutureTask<Void> showDialogTask = new FutureTask<Void>(showDialog, null);
               Platform.runLater(showDialogTask);
               showDialogTask.get();
            }
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            System.exit(-1);
        }
    };



    public static void main(String[] args) {
        System.setSecurityManager(new SecurityManager(){
            @Override
            public void checkPermission(Permission perm) {}
        });
        Thread.setDefaultUncaughtExceptionHandler(ALERT_EXCEPTION_HANDLER);
        Application.launch(App.class, args);
    }
}

and a test app:

import javafx.application.Application;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        throw new Exception("An exception");
    }

    @Override
    public void stop() {
        System.out.println("Stop");
    }

}

As I said, this is really something of a big hack, and I don't really recommend this unless you have no other option.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you for that answer! However I agree with you that it's quite a nasty workaround. I guess the better solution for me would be to make sure I have no unstable code in my start method and restrict the logic there to the basics, and then launch the other stuff in a new thread right before showing the stage. – user3237736 Mar 13 '16 at 00:45
  • edit: or actually, even better I just put all code from the start method in a try-catch block and show the error dialog and exit the app directly there. Only question that remains if this kind of FX shutdown happens also in other places than just the start method, e.g. when some event handling fails with an exception?! but I will test it, I guess it won't be the same there. – user3237736 Mar 13 '16 at 00:52
  • Yes, I was meaning to add either as a comment or in the answer that the fundamental difference here to Swing is that the `start()` method is really an integral part of the toolkit startup - so in a sense failure in the start method is fatal for the whole UI. Minimizing the code in `start()` and factoring everything else out elsewhere is good practice for a number of reasons. Once the start method is complete, there is no issue, so for example it works fine if an uncaught exception occurs in an event handler, etc. – James_D Mar 13 '16 at 00:53
  • See http://stackoverflow.com/questions/32464698/java-how-do-i-start-a-standalone-application-from-the-current-one-when-both-are for an example of why minimizing the code in `start()` is advisable, and http://stackoverflow.com/questions/32739199/javafx-software-design/32739723#32739723 for a really extreme case... – James_D Mar 13 '16 at 00:54
  • Thanks James for all your help! I consider the case closed – user3237736 Mar 13 '16 at 01:43