Search code examples
javajavafxreturnprogram-entry-point

why javafx ignore return from main and still launches an application?


I have the following code.

public static void main(String[] args)
{
    if (!ArgumentsHandler.handle(args))
    {
        return;
    }

    Storage.getInstance().load();

    if (!Storage.getInstance().isLoadSuccessful())
    {
        launch(args);
    }
    else
    {
        System.err.println("Unable to load configurations.");
    }
}

I've specifically inverter the condition inside the if statement to fail it and I can definitely see in the debugger that it does not execute the launch method, yet the application window is still being shown.

I've also notices that using return statement inside main method has no effect - application still continues to execute. It only responds to System.exit(0).

Why is this happening?

Update:

As you requested, here is a snippet of ArgumentsHandler. Nowhere here do I use threads (at least intentionally).

public static boolean handle(String[] args)
{
    //handle args
    if (args.length > 0)
    {
        switch (args[0])
        {
            //createRepository
            case "-c":
                configure(args);
                break;
            case "-r":
            case "--repository":
                repository(args);
                break;
            default:
                help();
                break;
        }

        return false;
    }

    return true;
}

private static void configure(String[] args)
{
    if (args.length > 1)
    {
        boolean isRandom = false;

        switch (args[1])
        {
            case "true":
            case "1":
                isRandom = true;
                break;
            case "false":
            case "0":
                //valid input, ignored
                break;
            default:
                System.err.println("Invalid arguments. Possible values: [--configuration] [1/0].");
                return;
        }

        Storage.configure(isRandom); //creates a bunch of json files (uses NIO).
        return;
    }
    else
    {
        System.err.println("Invalid arguments. Possible values: -c [1/0].");
    }
}

Storage

public void load()
{
    isLoadSuccessful = false;

    //load configuration
    app = loadConfiguration(appFilePath);

    if (app == null)
    {
        System.err.println("Unable to load app configuration.");
        return;
    }

    //load company
    company = loadCompany(app.getCompanyFilePath());

    if (company == null)
    {
        System.err.println("Unable to load company configuration.");
        return;
    }

    repository = loadRepository(app.getRepositoryFilePath());

    if (repository == null)
    {
        System.err.println("Unable to load repository configuration.");
        return;
    }

    isLoadSuccessful = true;
}

private static App loadConfiguration(String filePath)
{
    return (App) Utility.load(filePath, App.class);
}

loadConfiguration, loadCompany and loadRepository are really the same. In the future, they would not read simple json files, but would access complex archives that's why I've already created several nearly identical methods.

Utility.load

public static Object load(String path, Type type)
{
    try
    {
        JsonReader reader = new JsonReader(new FileReader(path));
        Gson gson = new Gson();
        Object obj = gson.fromJson(reader, type);
        reader.close();

        return obj;
    }
    catch (IOException ex)
    {
        ex.printStackTrace();
        return null;
    }
}

Just deserializes object from file.


Solution

  • From the way you are calling launch(args) I'm assuming, and you later confirmed this, that the main method is in a subclass of Application. I believe this is the cause of your issue.

    As you've noted there are a lot of seemingly JavaFX-specific threads running. Specifically, the non-daemon "JavaFX Application Thread" is running (at least, it's non-daemon in Java 10). This thread will cause the JVM to remain alive even if the main thread exits. This is normal behavior for Java:

    java.lang.Thread

    When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

    • The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
    • All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

    But why is the "JavaFX Application Thread" started when you have deliberately not called Application.launch yet? I'm just guessing here but it probably has to do with the special treatment JavaFX applications receive. Since at least Java 8 you don't have to declare a main method inside a subclass of Application1. If the main class is a subclass of Application Java handles the launching automatically.

    import javafx.application.Application;
    import javafx.stage.Stage;
    
    public class MyApp extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            // create scene and show stage...
        }
    
    }
    

    If you have the above and call java MyApp the application will launch and start will be called. However, if you have the below:

    import javafx.application.Application;
    import javafx.stage.Stage;
    
    public class MyApp extends Application {
    
        public static void main(String[] args) {}
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            // create scene and show stage...
        }
    
    }
    

    Then the main method is called but start is not. Basically, explicitly declaring main overrides the default behavior of launching the JavaFX application but doesn't stop the JavaFX runtime from being initialized. Maybe this behavior is as designed or maybe it's an oversight. But the important thing here is this only happens if the main class has a main method and is an Application subclass. If you separate those two:

    public class MyApp extends Application {
        // implement ...
    }
    
    public class Main {
        public static void main(String[] args) {
            // Perform pre-checks, return if necessary
            Application.launch(MyApp.class, args);
        }
    }
    

    Then you will no longer have this problem.

    Otherwise you can continue using System.exit() or switch to Platform.exit().


    There's another, maybe more appropriate, way to handle this. You seem to be performing initialization in the main method before calling Application.launch. If something goes wrong during this initialization you want to abort launching the JavaFX application. Well, JavaFX provides the means to do this itself: Application.init().

    The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application.

    The implementation of this method provided by the Application class does nothing.

    NOTE: This method is not called on the JavaFX Application Thread. An application must not construct a Scene or a Stage in this method. An application may construct other JavaFX objects in this method.

    Move your initialization code to this method. If you call Platform.exit() then the application will exit and Application.start won't be invoked. An alternative is to throw an exception inside init. You can also get the application arguments by using Application.getParameters() which returns an instance of Application.Parameters.

    public class MyApp extends Application {
    
        @Override
        public void init() throws Exception {
            if (!ArgumentsHandler.handle(getParameters()) {
                Platform.exit(); // or throw an exception
            } else {
                Storage storage = Storage.getInstance();
                storage.load();
                if (!storage.isLoadSuccessful()) {
                    Platform.exit(); // or throw an exception
                }
            }
        }
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            // Create Scene and show the primary Stage
        }
    
        @Override
        public void stop() throws Exception {
            /*
             * Called when the JavaFX application is exiting, such as from
             * a call to Platform.exit(). Note, however, that in my experience
             * this method is *not* called when Platform.exit() is called inside
             * the "init" method. It is called if Platform.exit() is called from
             * inside the "start" method or anywhere else in the application once
             * it is properly started.
             *
             * This is where you could perform any necessary cleanup.
             */
        }
    
    }
    

    1. JavaFX was included with Java SE in version 8. Note this behavior may change in Java 11 since JavaFX is to be separate from Java SE again.