11

I would like to close a javafx application with a specified return code. Browsing through answers on SO, I found the following idiom:

Platform.exit();
System.exit(0); 

for example here: Stop threads before close my JavaFX program
or here: JavaFX application still running after close

These two methods executed one after another look like we are attempting to duplicate some actions. I would assume, that if Platform.exit() is successful, it should not return to the place where System.exit(0) is called. If however Platform.exit() only triggers some closing action running on another thread, returns and System.exit(0) can be called then this may cause some race condition, where two threads are trying to close the same application.

So, how does this idiom exactly work?

Piotr G
  • 959
  • 1
  • 7
  • 25

3 Answers3

21

Calling System.exit(...) terminates the Java Virtual Machine.

As I understand it, calling Platform.exit() just signals the JavaFX Toolkit to shut down, resulting in the application instance's stop() method being called on the FX Application thread, and the FX Application Thread being allowed to terminate. This in turn causes Application.launch() to return. If you are using the usual idiom in your main(...) method:

public static void main(String[] args) {
    Application.launch(args);
}

then once launch() returns, there is nothing left for the main() method to do, and no (as long as no non-daemon threads are running) the application exits in a normal way. Platform.exit() does not create a call to System.exit(...) under any circumstances: however under certain circumstances it will allow the JVM to exit simply because there is nothing left for it to do.

If you call System.exit(...) the JVM basically exits immediately. So, for example, if you have code in the main(...) method after Application.launch(), that code gets executed after a call to Platform.exit(), but not after a call to System.exit(...). Similarly, if you override Application.stop(), the stop() method is called after a call to Platform.exit(), but not after a call to System.exit(...).

If you have non-daemon threads running, Platform.exit() will not forcibly shut them down, but System.exit() will.

The following example should demonstrate this:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ExitTest extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        Button startThread = new Button("Start non-daemon thread");
        startThread.setOnAction(e -> new Thread(() -> {
            System.out.println("Starting thread");
            try {
                Object lock = new Object();
                synchronized(lock) {
                    lock.wait();
                }
            } catch (InterruptedException exc) {
                System.err.println("Interrupted");
                Thread.currentThread().interrupt();
            } finally {
                System.out.println("Thread complete");
            }
        }).start());

        Button exit = new Button("Simple Exit");
        exit.setOnAction(e -> {
            System.out.println("Calling Platform.exit()");
            Platform.exit();
        });

        Button forceExit = new Button("Force exit");
        forceExit.setOnAction(e -> {
            System.out.println("Calling Platform.exit():");
            Platform.exit();
            System.out.println("Calling System.exit(0):");
            System.exit(0);
        });

        Scene scene = new Scene(new HBox(5, startThread, exit, forceExit));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
        System.out.println("launch() complete");
    }
}

It's generally recommended that you exit a JavaFX Application with a call to Platform.exit(), which allows for a graceful shutdown: for example if there is any "cleanup" code you need, you can put it in the stop() method and Platform.exit() will allow it to be executed. If you are running background threads which must be terminated, either make them daemon threads, or execute them via an executor service, and shut down the executor service from the stop() method. Here is a modification to the above example which uses this technique.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ExitTest extends Application {

    private final ExecutorService exec = Executors.newCachedThreadPool();

    @Override
    public void stop() throws InterruptedException {
        System.out.println("Stop called: try to let background threads complete...");
        exec.shutdown();
        if (exec.awaitTermination(2, TimeUnit.SECONDS)) {
            System.out.println("Background threads exited");
        } else {
            System.out.println("Background threads did not exit, trying to force termination (via interruption)");
            exec.shutdownNow();
        }       
    }

    @Override
    public void start(Stage primaryStage) {
        Button startThread = new Button("Start non-daemon thread");
        startThread.setOnAction(e -> { 
            exec.submit( () -> {
                System.out.println("Starting thread");
                try {
                    // just block indefinitely:
                    Object lock = new Object();
                    synchronized(lock) {
                        lock.wait();
                    }
                } catch (InterruptedException exc) {
                    System.out.println("Interrupted");
                    Thread.currentThread().interrupt();
                } finally {
                    System.out.println("Thread complete");
                }
            });
        });

        Button exit = new Button("Simple Exit");
        exit.setOnAction(e -> {
            System.out.println("Calling Platform.exit()");
            Platform.exit();
        });


        Scene scene = new Scene(new HBox(5, startThread, exit));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
        System.out.println("launch() complete");
    }
}

If you want to use Platform.exit() in order to have a graceful shutdown, and you want to return a value from System.exit(...), the following approach should work. Note that this is not really a recommended practice anyway: in production code you should not really rely on the platform supporting a process exit code at all.

public class App extends Application {

    private static int exitCode = 0 ;

    public static exit(int exitCode) {
        App.exitCode = exitCode ;
        Platform.exit();
    }

    @Override
    public void start(Stage primaryStage) {
        // ...

        someThing.addEventHander(someEventType, e -> App.exit(42));

        // ...
    }

    @Override
    public void stop() {
        // cleanup code...
    }

    public static void main(String[] args) {
        Application.launch(args);
        System.exit(exitCode);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you for that explanation. I wonder what you wrote about that "not recommended practice" in production code. Does it mean that production code should not return any other exitcode but 0? – Piotr G Sep 05 '17 at 18:53
  • 1
    @PiotrG You shouldn't rely on the exit code being propagated back to whoever launched the JVM, since it is effectively a platform-dependent feature. – James_D Sep 05 '17 at 19:03
  • Umm ... if you know that your production application is only going to be run on (say) Linux, it is not unreasonable to assume Linux-like handling of exit codes. – Stephen C Nov 06 '19 at 01:40
4

well the Doc is your friend in such a case:

calling System.exit(0) will terminate the JVM

Terminates the currently running Java Virtual Machine. The argument serves as a status code; by convention, a nonzero status code indicates abnormal termination. This method calls the exit method in class Runtime. This method never returns normally.

and doing Platform.exit() will terminates the FX application

Causes the JavaFX application to terminate. If this method is called after the Application start method is called, then the JavaFX launcher will call the Application stop method and terminate the JavaFX application thread. The launcher thread will then shutdown. If there are no other non-daemon threads that are running, the Java VM will exit. If this method is called from the Preloader or the Application init method, then the Application stop method may not be called.

sproketboy
  • 8,967
  • 18
  • 65
  • 95
ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
  • Well, what you have cited does not answer my question. Running ```Platform.exit()``` in case of non-daemon threads should proceed to closing JVM, so how does it return to the place where ```System.exit(0)``` is called? And if it returns, then both methods race to terminate JVM. – Piotr G Sep 05 '17 at 11:49
  • Sorry, I am not quite following you. By "the 2 sentences" do you mean "the second sentence", which is: "calling System.exit(0) will terminate the JVM"? Yes, System.exit(0) terminates JVM, but calling Platform.exit() first will cause exiting from JVM too. And I still do not know how can Platform.exit() return to the place where System.exit() is called. – Piotr G Sep 05 '17 at 11:59
  • 1
    Plus one for the last line. There are correct ways to do this; then there are hacks. These are hacks. – Boris the Spider Sep 05 '17 at 17:40
  • 1
    Ok, so what would be the correct way to close a JavaFX application returning a particular status code? – Piotr G Sep 05 '17 at 18:38
  • 3
    @BoristheSpider Is `Platform.exit()` a hack? It allows for a graceful shutdown, letting the FX Application Thread complete, and invokes the `stop()` callback method (assuming the application has `start()`ed), allowing for cleanup of resources. – James_D Sep 07 '17 at 01:18
1

Another approach, that avoids hacks, would be to check if any stages are open and close them using Stage.close(), and then call Platform.exit(). For example, in a two window app:

            if (secondWindow != null) secondWindow.close();
            Platform.exit(); 
Jason210
  • 2,990
  • 2
  • 17
  • 18