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.
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 classRuntime
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 therun
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 Application
1. 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.