hal
hal

Reputation: 841

Why javafx application fails to start with Platform.runLater and why hangs up with lambda expression?

I have simple javafx HelloWord application, but when i try to start it properly using Platform.runLater i receive java.lang.RuntimeException. Similarly when i try to use lambda expression, my frame doesn't show up. Program prints 'starting', but hangs up on showing the frame.

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

public class MainFrame extends Application {

    public static void main(String[] args) {

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                MainFrame.launch(args);
            }
        });

        Platform.runLater(() -> {
            System.out.println("starting");
            launch(args);
            System.out.println("started");
        });
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction((e)->System.out.println("Hello World!"));

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

This is entire output produced by the application:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: 
Error: class MainFrame$1 is not a subclass of javafx.application.Application
    at javafx.application.Application.launch(Unknown Source)
    at MainFrame$1.run(MainFrame.java:15)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(Unknown Source)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
starting

Could anyone explain to me why i receive exception and why lambda expression hangs up application?

Upvotes: 2

Views: 1524

Answers (1)

James_D
James_D

Reputation: 209388

Application.launch(args) is equivalent to Application.launch(TheClass.class, args), where TheClass is the immediately enclosing class of the method call. TheClass must be a subclass of Application, else an IllegalArgumentException is thrown. The immediately enclosing class in your first code block is the anonymous inner Runnable subclass that is not itself a subclass of Application, so you get the IllegalArgumentException.

Your assumption that the proper way to start a JavaFX application is by calling launch() from the FX Application Thread is incorrect. In fact, the FX Application Thread will not be running until the FX toolkit is started, and that will not happen until launch() is called. Hence your second code block just doesn't do anything: there is no FX Application Thread on which to execute launch(). In addition, the launch() method blocks until the application exits (again, see documentation); so even if the FX Application Thread were running(*), you would simply deadlock it as it would be blocked, waiting for its own exit.

The "proper" way to start a JavaFX Application is simply to call launch() from the main thread (or any other thread, but not the JavaFX Application Thread):

public class MainFrame extends Application {

    public static void main(String[] args) {
        System.out.println("Calling launch from main thread: "+Thread.currentThread());
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        System.out.println("start() invoked on thread: "+Thread.currentThread());
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction((e)->System.out.println("Hello World!"));

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

Calling launch() here will start the FX toolkit, start the FX Application Thread and create an instance of MainFrame. Then, on the FX Application Thread, it will create a Stage and pass it to the MainFrame instance's start() method.

(*) I think the way you have your code, the first failed attempt to call launch() will actually start the FX Toolkit, so the second attempt does call launch() on the FX Application Thread, causing deadlock as described.

Upvotes: 2

Related Questions