javafxuncaughtexceptionhandler

JavaFX: How to show error dialog when start method fails?


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?


Solution

  • 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.