Issue
To my dismay I have realized my open source ScalaFX application does not run with JDK 11. The application was developed with JDK 8. I have read about a JFX module and I can see there exist versions of ScalaFX dedicated to JDK 11 and newer (ScalaFX 12). The way I understand it it does not achieve what I want - I would have to build different versions of my application for JDK 8 and JDK 11.
Is there a way to make a single ScalaFX application working on both JDK 8 and 11?
Solution
UPDATE: I've both simplified and expanded upon my original answer.
To address your main question, I believe it should be possible to write a single ScalaFX 8 application that targets both JavaFX 8 and JavaFX 9+, provided that you stick to the API defined by the former. As a result, I would recommend compiling your application using a Java 8 JDK with ScalaFX/JavaFX 8. If you use a Java 9+ JDK, or build against any version of ScalaFX higher than 8, then you'll be unable to run your application with a Java/JavaFX 8 run-time.
(If you want to support new features of ScalaFX/JavaFX, then you have no option but to target the specific version in which they appear, which may require you to produce multiple versions of your application if you still need to support older releases. Since you've stated that that's not your goal, I'm targeting ScalaFX/JavaFX 8 here.)
Note that Java versions 8 and 11 are long-term support (LTS) releases; Java versions 9, 10 and 12 are not, and are more bleeding-edge.
As you're already aware, there are fundamental differences between Java 8 and the subsequent Java versions, following the introduction of Jigsaw modules in Java 9. Scala support for modules is still very primitive, and it currently more-or-less pretends that they don't exist, which actually helps in this case.
The primary issue that needs to be overcome is configuring the Java Virtual Machine (JVM) when running your application.
Incidentally, note that I'm using the term JavaFX to include OpenJFX, which is the open-source version of JavaFX.
When running your application with JavaFX 8, there will be a single JAR file named jfxrt.jar
, and all you need to do is ensure that you have that file on your classpath. This is basically the same requirement that your application has always had, so you should be good here. Incidentally, JavaFX 8 should still work fine with Java 9+, as Java 9+ needs to maintain compatibility with older, non-modular libraries.
However, when running your application with JavaFX 9+, things get a little more interesting...
Firstly, as a rule of thumb, if you're using JavaFX version X (where X is some version higher than 8), you typically must also use a JVM whose version is equal to or higher than X. That is, you should be OK running JavaFX 9 with a JVM version 11, for example. However, you generally cannot expect to use, say, JavaFX 11 with JVM version 9.
While JavaFX 8 has a single JAR file that includes the whole library, versions 9+ are packaged into a number of separate modules, each with their own JAR file:
javafx-base
javafx-controls
javafx-fxml
javafx-graphics
javafx-media
javafx-swing
javafx-web
.
If you don't need, say, the elements defined in javafx-swing
, then you can ignore the corresponding JAR file. However, to play it safe, you'll likely need to require all of them, which is what I'll do here.
You need to tell the JVM that these are modules, and to load them when the application runs.
For example, let's say you've installed JavaFX 11 into a directory named C:\JavaFX11
on your machine, such that the library's JAR files are located at C:\JavaFX11\lib
. Let's also say that I have an environment variable, JAVAFX_HOME
that identifies this latter directory. You now need to pass the following arguments to the JVM when running your application:
--module-path %JAVAFX_HOME%
--add-modules javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web
These options are only valid for JVM's of version 9 or higher. (Clearly, this is also for Windows; you'll need to do something similar for other platforms.)
If you do not do this, then you'll likely see an error stating Error: JavaFX runtime components are missing, and are required to run this application
. (If you're seeing something different, can you update your question to show the results of running your ScalaFX 8 application under JDK 11?)
Of course, the trick is to have the script that runs your application detect the version of JavaFX installed. It might help if you require your user to define a JAVAFX_HOME
environment variable that specifies the location of their JavaFX library installation. If it contains a file named jfxrt.jar
, then you're using version 8, otherwise, version 9+. In the latter case, you might also want to verify that you're using a version 9+ JVM before supplying the additional arguments.
There are still some potential banana skins (that can result in run-time errors) if you violate any of the module-restrictions defined in any libraries packaged as modules. Provided you stick to the published JavaFX 8 API specification, and do not try to access private implementation classes, you should be good.
UPDATE 2: Address further comments.
I am not interested in any newer features, my only goal is to make old Java 8 application runnable with new JDKs, including JDK 11 and 12. If I would like to distribute JFX 8 (a single jar
jfxrt.jar
you have mentioned), how do I get it? I found maven artifacts only for JFX 11 and newer, downloads at gluonhq.com/products/javafx also start with 11)
As you're aware, JavaFX 8 is bundled as part of the Oracle Java 8 run-time environment (RTE). This Oracle version of JavaFX is, in any case, proprietary and distribution of it with your product is probably unwise.
Also, as you're probably aware, the OpenJDK 8 RTE release does not bundle JavaFX, leaving users to install OpenJFX 8 themselves. In this latter case, there is no artifact available in any repository for this release, as it wasn't until modularization that it became possible to package JavaFX.
My understanding is that Gluon only recently took on supporting JavaFX (as OpenJFX), making standalone downloads available, and providing packaged artifacts available on the Maven Central Repository, only as of JavaFX 11. Prior to them stepping up, it was the responsibility of the Oracle controlled OpenJFX project. The latter still hosts the official Mercurial-based code base, but have updated most of their links, including download links, to the Gluon site.
It appears from Googling "OpenJFX 8 download" that there are not too many options available, but building your own version from the sources is one possibility. Using something like the wayback machine is another. If you're running on Linux, a number of distributions still provide OpenJFX 8 releases in their repositories.
Maybe you could report the lack of a JavaFX 8 run-time to Gluon and see if they're prepared to release one?
However, there's a bigger problem brewing: the main issue you face is that JavaFX is not a pure Java library. It contains a number of platform-specific libraries that are referenced from the JavaFX JAR file(s). Just bundling your own version of jfxrt.jar
will not work, as the underlying libraries for each platform will be missing.
Even if you can overcome that issue, you would need to ship a different version for each supported platform (Windows 64-bit, Windows 32-bit, MacOs, etc.) with the appropriate bundled version of OpenJFX.
You might want to consider dropping support for JavaFX 8, since you can then add platform-dependent library dependencies to your application build, and use an SBT plugin such as sbt-native-packager
to bundle your JavaFX dependencies into platform-specific installation pacakages.
If you still need to support JavaFX 8, you currently have little option but to rely upon the user providing JavaFX themselves.
I have tried a JDK 8 directory containing
jfxrt
(which isJava\jdk1.8.0_161\jre\lib\ext
for me) to the classpath of the application running under JDK 11 and the application was unable to start, with the exception "java.lang.RuntimeException: Exception in Application start method" with additonal information "Caused by: java.lang.NoClassDefFoundError: sun/misc/SharedSecrets"
It's hard to say exactly what is causing this problem, without seeing your code, how it was run, and the full output from the application. Can you update your questions to include that information?
Answered By - Mike Allen
Answer Checked By - Gilberto Lyons (JavaFixing Admin)