Different behaviour between Maven & Eclipse to launch a JavaFX 11 app
Asked Answered
J

1

3

I'm starting to dig into Java 11 migration for a large app (includes Java FX parts) and I need your help to understand the difference between Maven (3.5.4) on the command-line and Eclipse (2018-09 with Java11 upgrade).

I have a simple Java 11 class

import java.util.stream.Stream;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(l, 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Stream.of("jdk.module.path",
                "jdk.module.upgrade.path",
                "jdk.module.main",
                "jdk.module.main.class").forEach(key -> System.out.println(key + " : " + System.getProperty(key)));

        Application.launch();
    }

}

and a simple pom

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gluonhq</groupId>
    <artifactId>hellofx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>HelloFX</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

When I run 'mvn compile exec:java' I think nothing uses the new module-path and the program displays the JavaFX panel as expected. The system out is:

jdk.module.path : null

jdk.module.upgrade.path : null

jdk.module.main : null

jdk.module.main.class : null

When ran from an Eclipse launcher, I have to add to the launcher the following vm arguments:

--module-path=${env_var:JAVAFX_PATH} --add-modules=javafx.controls

and the panel is also displayed but the output is:

jdk.module.path : C:\dev\tools\javafx-sdk-11\lib

jdk.module.upgrade.path : null

jdk.module.main : null

jdk.module.main.class : null

jdk.module.main.class : null

I cannot make it work in Eclipse as it works from the command line: I am forced to mess with the modules and module-path. If I do not add the vm parameters, I got either "Error: JavaFX runtime components are missing, and are required to run this application" or "Error occurred during initialization of boot layer java.lang.module.FindException: Module javafx.controls not found".

How can it work form the command-line without any more configuration ? To my knowledge Maven do not add automagically anything to the module path...

Any idea ? What am I missing ?

Update1: I realized that when importing the project in Eclipse "as Maven project" (which is what I always do) it results in the JRE being added in the module path (which is not the case for my classis projects). See the screenshot enter image description here

Jem answered 10/10, 2018 at 13:14 Comment(0)
S
1

When running from command line, if you are choosing the Maven (same works for Gradle) build system, you let the plugins do the work for you.

When you run from your IDE the main class, but not from the built-in Maven/Gradle windows, on the contrary, you are running the plain java command line options.

And these results in two different things (but with same final result of course), as you already have figured out via the properties print out.

As already covered by this answer for IntelliJ, but applies to any other IDE, or this other one for Eclipse, there are two ways of running a JavaFX 11 project, based on the use or not of the Maven/Gradle build system.

JavaFX project, without build tools

To run your JavaFX project from your IDE, you have to download the JavaFX SDK and add a library with the different javafx jars to your IDE, with a path like /Users/<user>/Downloads/javafx-sdk-11/lib/.

Now, to run that project, even if it is not modular, you have to add the path to those modules, and include the modules you are using to the VM options/arguments of the project.

Whether you run the project from your IDE or from command line, you will be running something like:

java --module-path /Users/<user>/Downloads/javafx-sdk-11/lib/ \
    --add-modules=javafx.controls org.openjfx.hellofx.HelloFX

Note that even if your project is not modular, you are still using the JavaFX modules, and since you are not using any build tool, you have to take care of downloading the SDK in the first place.

JavaFX project, build tools

If you use Maven or Gradle build tools, the first main difference is that you don't need to download the JavaFX SDK. You will include in your pom (or build.gradle file) what modules you need, and Maven/Gradle will manage to download just those modules (and dependencies) to your local .m2/.gradle repository.

When you run your main class from Maven exec:java goal you are using a plugin, and the same goes for the run task on Gradle.

At this point, it looks like when you run:

mvn compile exec:java

or

gradle run

you are not adding the above VM arguments, but the fact is that Maven/Gradle are taking care of it for you.

Gradle

In the Gradle case, this is more evident, since you have to set them in the run task:

run {
    doFirst {
        jvmArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'javafx.controls'
        ]
    }
}

While you don't need the SDK, the classpath contains the path to your .m2 or .gradle repository where the javafx artifacts have been downloaded.

Maven

For Maven, while the pom manages the dependencies of the different javafx modules, and sets the classifier to download the platform-specific modules (see for instance /Users/<User>/.m2/repository/org/openjfx/javafx-controls/11/javafx.controls-11.pom), the plugin manages to configure the classpath and create the required options to run the project.

In short, a new class that doesn't extend Application is used to call your application class: HelloFX.main(args).

EDIT

See this answer for a more detailed explanation on why launching a JavaFX application without module-path fails. But in short:

This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module. If that module is not present, the launch is aborted.

A more detailed explanation on how the maven plugin works without setting the module-path:

If you add debug level (default is info) when running the Maven goals, you will get more detailed information on what is going on behind the scenes.

Running mvn compile exec:java shows:

 ...
[DEBUG]   (f) mainClass = org.openjfx.hellofx.HelloFX
 ...
[DEBUG] Invoking : org.openjfx.hellofx.HelloFX.main()
 ...

And if you check the exec-maven-plugin source code, you can find at ExecJavaMojo::execute how the main method of the Application class is called from a thread.

This is exactly what allows launching an Application class from an external class that does not extend Application class, to skip the checks.

Conclusion

Is up to you to choose build tools or not, though nowadays using them is the preferred option, of course. Either way, the end result will be the same.

But it is important to understand what are the differences of those approaches, and how your IDE deals with them.

Schmit answered 10/10, 2018 at 15:35 Comment(13)
Thanks for your long response José. nevertheless I am still a bit lost. Especially when you say that "[build tools] are taking care of it for you". It seems not true, especially since I discovered that I was using an exec-maven-plugin from 2011 (not on purpose)! This 1.2.1 version knows nothing about module-path or any new Java 11 stuffs. That being said, it does not prevent it from running JavaFX 11. What is the trick ?! Why does the IDE needs a specific configuration with module-path while the command line does not ?Jem
I explained how that works at the Maven part: a new class that doesn't extend Application is used to call your application class: HelloFX.main(args). Providing you have the right dependencies in your classpath, you can always run that class without the module-path/add-modules flag, and launch your JavaFX project.Ironmaster
See this question for an explanation on how you can use that "launcher" class to create a uber/fat jar, for instance. It is the same mechanism that Maven uses with the plugin.Ironmaster
I'm sorry José but I'm still missing the point: as the code shows, in the HelloFX (extending Application) I have a classic "main" method. whether it is Maven or eclipse, they both call this method as they have always done in Java. What magic trick would make Eclipse or Maven auto-detect JavaFX and make them do something different ?Jem
(for the record, in eclipse I import the project as a Maven project so m2e is supposed to create the classpath exactly as Maven does on the command line, but it seems the JRE is added as module path - see the screenshot I will add in my original post) (again, sorry for insisting so much but I need to understand the whole mechanism in details to explain it to the team ;)Jem
I've edited my answer with more details on how the plugin works without using module-path.Ironmaster
THANK YOU ! I'm a little slow but I needed this technical detail ;) Thanks for your time. One last question if you have time: do you know WHY the LauncherHelper does this check while it seems that JavaFX apps are able to run (see the Maven case) even if we do not use the module-path ? I mean, is there a real reason why you would force users to use module-path while it would work perfectly well even if you did not use it ?Jem
That is for another question... but in short, since Java 9 there is an FXHelper class that goes through the com.sun.javafx.application.LauncherImpl way instead of via main.Ironmaster
I found this: mail-archive.com/[email protected]/msg13956.html I feel like some things may not work if not using modules. Nevertheless, it's strangeJem
Yes, I'm aware of that, it was a response to the post I linked in one of the mentioned answers. If you want, you can follow this related issue.Ironmaster
Let us continue this discussion in chat.Jem
@JoséPereda I have read the tip to use an extra class with the main method which calls an class extending application multiple times now. But only in conjunction with running the programm via maven command mvn compile exec:java .How can i run the program inside eclipse with the green run icon? Or run it in eclipse Debug mode? NOT OUTSIDE IN CONSOLE IN ECLIPSE?Krefeld
Please post a new questionIronmaster

© 2022 - 2024 — McMap. All rights reserved.