JavaFX unter Java 11 - Ohne JPMS

Mit der Einführung von Java 11 wurde JavaFX auch aus dem Orcale JDK entfernt. Seit dem wird JavaFX unabhängig vom JDK entwickelt, und muss entweder eigenständig auf dem System installiert oder als Abhängigkeit über z.B. Maven eingebunden werden.

Abhängigkeiten über Maven einbinden

Am einfachsten lässt sich JavaFX über Maven (oder vergleichbare Build-Tools wie gradle) einbinden, auf dem System selbst muss JavaFX dann nicht mehr installiert sein.

Dazu fügt man die nötigen Teile einfach der pom.xml hinzu. Mindestens sollten dies javafx-graphics für die Grafik und javafx-controls für Controls wie z.B. Buttons, außerdem wird in vielen Fällen auch fxml benötigt. Als Beispiel können die nötigen Dependencies also so aussehen:

<dependencies>
  <dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-graphics</artifactId>
    <version>11</version>
  </dependency>
  <dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>11</version>
  </dependency>
  <dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>11</version>
  </dependency>
</dependencies>

(javafx-fxml bindet selbst javafx-controls und dieses javafx-graphics ein, es würde also auch reichen, nur letzteres einzubinden. Oft ist es aber ratsam, genutzte Dependencies explizit zu machen)

Code

JavaFX selbst ist vollständig modularisiert, lässt sich aber auch unter Java 11 weiter ohne das JPMS nutzen. Allerdings muss man den üblichen Start der Applikation etwas anpassen.

Üblicherweise befindet sich die main-Methode einer JavaFX-Application in einer Klasse, die javafx.application.Application erweitert, die main-Methode selbst delegiert dann nur noch an die launch.

Unter Java 11 ist das so ohne Modularisierung der eigenen Anwenundung nicht möglich. Da JavaFX modularisiert ist, wird es in dem Fall als Modul geladen, da aber entsprechende exports- und opens-Anweisungen fehlen, kann es auf manche nötige Klassen nicht zugreifen.

Umgehen lässt sich dies, wenn sich die main-Methode in einer anderen Klasse befinden. Sowohl die eigenen Klassen als auch JavaFX werden dann über den normalen Classpath und nicht über das Modulsystem geladen, sodass Zugriffe zwischen den verschiedenen Teilen möglich sind.

Die Application-Klasse kann man dabei wie üblich erweitern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class JavafxMain extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX on JDK 11");
        Parent root = new VBox(new Label("Hello World!"));
        Scene scene = new Scene(root, 400, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

Zusätzlich führt man eine weitere Main-Klasse ein:

1
2
3
4
5
public class Main {
    public static void main(String[] args) {
        JavafxMain.main(args);
    }
}

Führt man die ursprüngliche main aus, kommt es zu erwartbaren Fehlern:

Caused by: java.lang.IllegalAccessError: superclass access check failed: class com.sun.javafx.scene.control.ControlHelper (in unnamed module @0x573e85a8) cannot access class com.sun.javafx.scene.layout.RegionHelper (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.scene.layout to unnamed module @0x573e85a8

Führt man allerdings die neue main aus, wird die Applikation korrekt gestartet:

Running application

Packaging mit maven-assembly-plugin

Die endgültige Anwendung besteht nicht nur aus den eigenen Code, sondern auch aus den genutzten Abhängigkeiten. Ein einfaches Packen als Jar reicht also nicht aus - über Maven-Plugins lassen sich aber viele weitere Möglichkeiten einbinden.

Eine davon ist das maven-assembly-plugin, dieses packt sowohl den eigenen Code als auch alle nötigen Dependencies in eine Jar, die danach mit java -jar path/to/jar startbar ist.

Das Plugin muss man einfach in der pom.xml einbinden und die Main-Klasse inklusive Package entsprechend angeben:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <appendAssemblyId>false</appendAssemblyId>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
      <manifest>
        <mainClass>de.ybroeker.Main</mainClass>
      </manifest>
    </archive>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>attached</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Über die Konsole kann man die Anwendung dann mit mvn clean package bauen, die in target/ liegende jar ist danach ausführbar.