Issue
I can embed JavaFx in a Swing application when using OpenJfx. However as soon as I try to start a native Fx application via Application.launch(args) it is not possible to run it without pointing to a unpacked JavaFx library.
Following Swing application gets launched and shows some JavaFx components embedded, I can start this with a simple java call all necessary openjfx libraries included in a classpath argument, the call would look like this:
java -classpath [all openjfx libraries (also with platform classifier included)] DialogSwing
import java.awt.BorderLayout;
import javax.swing.*;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.effect.Reflection;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
public class DialogSwing extends JDialog {
private final JPanel contentPane = new JPanel(new BorderLayout());
public DialogSwing() {
setContentPane(contentPane);
final JFXPanel panel = new JFXPanel();
Platform.runLater(() -> {
try {
contentPane.add(panel);
StackPane stack = new StackPane();
Scene scene = new Scene(stack, 300, 300);
Text hello = new Text("Hello");
scene.setFill(Color.BLACK);
hello.setFill(Color.WHEAT);
hello.setEffect(new Reflection());
panel.setScene(scene);
stack.getChildren().add(hello);
} catch (Exception e) {
e.printStackTrace();
}
}
);
}
public static void main(String[] args) {
DialogSwing dialog = new DialogSwing();
dialog.setSize(800, 600);
dialog.setVisible(true);
System.exit(0);
}
private void onCancel() {
dispose();
}
}
Follow Fx application does not get started until I specify full pointer to a openjx lib dir via the --module-path parameter as well as a prism library, so this is not working:
java -classpath [all openjfx libraries (also with platform classifier included)] DialogFx
But this is working:
java -classpath [all openjfx libraries (also with platform classifier included)] --module-path /usr/share/openjfx/lib/ -Djava.library.path=/usr/lib/x86_64-linux-gnu/jni/ --add-modules=javafx.controls DialogFx
(The library path specified contains a working prism library also needed.)
import javafx.application.Application;
import javafx.stage.Stage;
public class DialogFx extends Application {
public void start(Stage stage) throws Exception {
}
public static void main(String[] args) {
Application.launch(args);
}
}
Final note: The difference described above in the Fx Java call might look subtle, but this means it is not possible running a JavaFx application without unpacked Fx environment which makes things complicated. But why is this needed if JavaFx is available in a Swing environment with ease. I can imagine there are other side affects like lower performance but this is an other topic of course.
The standard JDK for all this is a plain OpenJdk 15.
Solution
Recomendations
Don't run JavaFX from the class path, run it with the relevant JavaFX modules on your module path.
You also don't need to specify a library directory for JavaFX native libraries when you have the JavaFX module jars classified for your platform on the module path.
- Platform specific JavaFX modules on the module path encapsulate native libraries that they use. The platform can find the native libraries in the modules without additional assistance.
Why these recommendations are made
Running from the class path is not a supported configuration. See:
For more information, refer to the writeups in these answers:
- JavaFX WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @...'
- Maven Shade JavaFX runtime components are missing
As noted in comments and those answers:
- A JavaFX Application class run directly with the JavaFX classes on the classpath will fail fast with an error.
- You can work around that with a separate Launcher class hack, and it the application will run with a warning that it is unsupported (at least with JavaFX 17).
Running in a Swing embedded mode via JFXPanel might bypass those other Application lifecycle checks that validate that classes are loaded from the module path, which is likely why you observe that it appears to just work with the JavaFX modules on the classpath.
However, running JavaFX from the classpath is still an unsupported configuration so it is not officially recommended, even if it seems to be working OK for you in some modes of execution.
Official Documentation
Documentation of recommended execution modes for JavaFX is provided in:
FAQ
- I am showing JavaFx can be embedded in Swing without any modules specified.
Even if it runs in such a configuration, it is not supported and it is recommended that JavaFX modules be on the module path, not the classpath.
- Running without specifying module-path (native code pointer) an Application can not run, at least in OpenJDK not bundled with JavaFx by default
Yes, that is expected and by design. Attempting to run a JavaFX application without the JavaFX modules on the module path is not a supported configuration.
As I understand the deeper design goal is to keep Java platform-independent instead of heavy UBER jar packaging with native libraries involved.
No.
Native libraries are required for a JRE and for frameworks like JavaFX, it can't be a design goal to not involve them. Instead, the build, package, deploy, execution pipeline needs to take native libraries into account.
That is why, when the JDK was modularized, it included the ability to:
- Encapsulate native libraries in modules (see the jmod tool and the Java module format).
- Create native images of the modules (see the jlink tool and the Java image format).
- Create native installers (see the jpackage tool). These are all tools for creating and working with Java bytecode, resources, and native libraries.
These are all tools for creating and working with Java bytecode, resources, and native libraries.
There is a great writeup on the difference between a non-modular jar, a jmod, and jimage packaging here:
I encourage you to read, study and experiment with Java modules and the associated toolchains to get a better understanding of the Java module system.
What strikes me is the hard-coded module-path setting needed. Is there a possibility for Java to rely on system standard paths and check/search for JFX environment available? So it remains flexible in this matter? I think this would be the task of the installed JRE isn't it?
Yes, but the key is that it is suggested that you create and provide the JRE to be installed as part of your application packaging. Then, you can be sure that a complete and compatible JRE is correctly installed for the application.
The recommended packaging mechanisms are jpackage, jlink or native image. Those methods provide self-contained application installers or bundles that contain the JRE with all required modules: your app code and resources, dependent libraries, the JRE, JavaFX modules and a startup script.
To gain a better understanding of how those distribution options work, create one of the recommended packages.
Also, alternatively, as noted by Slaw in the comments, you can also use a JDK that includes JavaFX and have a pre-requisite for your application use that your end-users install a JRE that includes JavaFX. Some distributions that provide these for JDK/JavaFX 17 are Liberica "Full JDK/JRE" or Azul "JDK/JRE FX".
What is the conceptual difference of jlink or jpackage to classpath packaging, why is classpath packaging to be avoided?
Items on the module path are Java modules, items on the classpath are not modules.
There are a lot of differences between the two setups in terms of:
- Encapsulation
- modules can only access required modules.
- Reflection handling
- modules must open their packages to reflection.
- Class loading functions
- classes on the module path only look for classes and resources in their dependent modules, not on the classpath.
- Accessibility
- module scope is more restrictive than classpath loaded classes.
- Security
- modules have lower visibility and attack surfaces.
- Packaging
- modules introduce jmod for providing modules packaged in jar format that include and module descriptors and can load native code from the module, rather than a lib directory.
- the jimage format allows multiple modules in a single file.
- Analysis tools
- jdeps can provide insight into available modules their descriptors and dependencies.
- Build tools
- jmod and can create modules, optionally mixing java code and native code, other standard tools like java and javac are module aware and their execution modes differ depending on whether or not you are running on the classpath.
- Distribution mechanisms
- modules can be linked to create an image or custom JRE via jimage, but libraries on the classpath cannot.
- Both modules and items on the classpath can be packaged via jpackage, which can create a package based on an existing JRE image from jlink or one it creates which includes both the modules in the JDK and your application and its dependent modules.
There are many differences and some are really significant.
Because of this, since JavaFX was released as modules with the JavaFX 11 release, the JavaFX development team only supports JavaFX deployed as modules and not when it is deployed on the classpath.
This support policy for JavaFX modules is similar to support policy for the JDK/JRE itself. After Java 9, the core JDK and JRE systems are only available as modules on the module path or assembled into the boot module path using a jimage. The original (pre Java 9) configuration of running the JDK/JRE classes off the classpath via rt.jar or tools.jar is no longer available or supported. So, similarly (post Java 11), the corresponding jfxrt.jar is no longer available or supported. Unlike modules, those JRE/JDK/JavaFX jar files only ever included the class files and the native library files had to be shipped separately and placed on the lib path (this was usually done via a monolithic JDK/JRE installation). Modules can still use native libraries on the lib path (I believe), but can also use native libraries packaged in the module or JRE image itself, which allows them to be more self-contained.
Answered By - jewelsea
Answer Checked By - Cary Denson (JavaFixing Admin)