Quarkus FX

This extension allows you to use Java FX in your Quarkus application.

Installation

In order to use this extension, you need to add the io.quarkiverse.fx:quarkus-fx dependency to your project.

Maven
<dependency>
    <groupId>io.quarkiverse.fx</groupId>
    <artifactId>quarkus-fx</artifactId>
    <version>0.12.0</version>
</dependency>
Gradle
dependencies {
    implementation 'io.quarkiverse.fx:quarkus-fx:0.12.0'
}

Usage

The extension allows using CDI features in JavaFX controller classes.
The FXMLLoader is made a CDI bean and can be injected in your application.

Loading FXML with injected FXMLLoader
@Inject
FXMLLoader fxmlLoader;

public void load() {
  Parent root = this.fxmlLoader.load(this.getClass().getResourceAsStream("/app.fxml"));
  Scene scene = new Scene(root);
  stage.setScene(scene);
  stage.show();
}
Controller
public class MyFxmlController {

  @Inject
  MyService myService;

  @FXML
  private void onButtonClicked() {
    this.myService.doSomething();
  }
}

Startup

The application will automatically be launched (thanks to a call to javafx.application.Application::launch) when the extension is present. With Quarkus Desktop SWT, JavaFX runs embedded in SWT instead, see JavaFX embedded in SWT.

If you need to customize the launch, you can provide a custom @QuarkusMain, and let Quarkus FX launch the JavaFX application :

Custom main
package org.acme;

import io.quarkiverse.fx.QuarkusFxApplication;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class MyMain implements QuarkusApplication {
    @Override
    public int run(String... args) {
        // Custom code, run before the JavaFX application is launched
        return new QuarkusFxApplication().run(args);
    }
}

QuarkusFxApplication::run launches the JavaFX application and returns when Quarkus exits : call Quarkus.asyncExit() to exit the application, for example when its primary stage is hidden. Quarkus FX then fires FxShutdownEvent on the FX thread and exits JavaFX itself: do not call Platform.exit() as well. An application that calls Platform.exit() before Quarkus.asyncExit() still exits at once, but FxShutdownEvent is then fired only when Quarkus FX detaches before JavaFX has exited, which is not guaranteed.

Do not call Application::launch in run. In a macOS native executable, run is called on the first thread of the process, which must run the macOS event loop for the application to show its windows : Application::launch blocks that thread, and the application never shows a window. QuarkusFxApplication::run launches the JavaFX application from another thread and, in a macOS native executable, runs the macOS event loop on the first thread until JavaFX exits. With Quarkus Desktop AWT or Swing, which keep the first thread in the macOS event loop themselves (as the java launcher does), run is called on another thread, and launches the JavaFX application as in JVM mode.

When the application is started, two main events are fired in sequence :

  • FxApplicationStartupEvent indicates that the Application is created and available,

  • FxPostStartupEvent indicates that the Application is ready as well as the Stage instance.

void onApplicationStartup(@Observes FxApplicationStartupEvent event) {
  Application application = event.getApplication();
}
void onPostStartup(@Observes FxPostStartupEvent event) {
  Stage stage = event.getPrimaryStage();
}

Injecting HostServices

The HostServices instance which is obtained via Application::getHostServices can also be injected by using the @Inject annotation.

@ApplicationScoped
public class MyBean {

  @Inject
  HostServices hostServices;
}

Accessing the primary stage

The primary Stage can be accessed by injecting FxViewRepository.

@ApplicationScoped
public class MyBean {

  @Inject
  FxViewRepository viewRepository;

  void doSomething() {
    Stage primaryStage = this.viewRepository.getPrimaryStage();
    primaryStage.setTitle("App Title");
  }
}

JavaFX embedded in SWT

An Eclipse SWT application can show JavaFX scenes with javafx.embed.swt.FXCanvas, an SWT widget (and convert images with SWTFXUtils), in JVM mode and in native executables, with the quarkus-desktop-swt extension of Quarkus Desktop, which runs the SWT user interface (see its documentation). It needs Quarkus Desktop 0.2.1 or later and Quarkus 3.40 or later, and javafx-swt (the javafx.swt module, see javafx-swt), in the JavaFX version of the application :

<dependency>
    <groupId>io.quarkiverse.desktop</groupId>
    <artifactId>quarkus-desktop-swt</artifactId>
    <version>0.2.1</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-swt</artifactId>
    <version>25.0.4</version>
</dependency>

The first FXCanvas starts JavaFX on the SWT user interface thread, which becomes the JavaFX Application Thread : create the FXCanvas once the user interface runs, for example in an SwtStartupEvent observer, and before any JavaFX control (a control needs JavaFX started).

@ApplicationScoped
public class MainWindow {

  void onStartup(@Observes SwtStartupEvent event) {
    Shell shell = new Shell(event.display());
    shell.setLayout(new FillLayout());
    FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
    canvas.setScene(new Scene(new Label("JavaFX in SWT")));
    shell.open();
  }
}

With Quarkus Desktop SWT, Quarkus FX :

  • does not launch a JavaFX application : Quarkus Desktop SWT runs the user interface, with its main application when there is an SwtStartupEvent observer, or SwtLifecycle.run() in your @QuarkusMain (see its documentation). A JavaFX application launched next to SWT does not work (on macOS, SWT crashes when it creates its Display) : QuarkusFxApplication.run fails,

  • keeps JavaFX running when the last FXCanvas is closed (Platform.setImplicitExit(false)) : JavaFX cannot start again in the JVM. The application exits with SWT, by default when its last shell is closed,

  • runs the @RunOnFxThread methods with Platform.runLater, once an FXCanvas has started JavaFX (they fail with IllegalStateException before),

  • configures the native executable for FXCanvas and SWTFXUtils, in addition to SWT, which Quarkus Desktop SWT configures.

The JavaFX Application Thread is the SWT user interface thread : the @RunOnUiThread methods, UiThread and UiThreadExecutor of Quarkus Desktop SWT run there too. FxApplicationStartupEvent, FxPostStartupEvent, FxShutdownEvent, FxStartupLatch, the HostServices injection, the primary stage of FxViewRepository and the @FxView views rely on the JavaFX application launched by Quarkus FX : they are not available. Load FXML documents with an injected FXMLLoader.

JavaFX starts once per JVM, bound to the thread that started it : after a restart of dev mode, or in a second application started in the same JVM (tests), FXCanvas does not work. Restart dev mode instead (q, then quarkus:dev again), and test each application in its own JVM, for example with @QuarkusMainIntegrationTest. As for SWT, macOS needs -XstartOnFirstThread in JVM mode, and dev mode runs no user interface on macOS (see the documentation of Quarkus Desktop SWT). With quarkus-desktop-awt or quarkus-desktop-swing too, use the same version of Quarkus Desktop (0.2.1 or later), see Using AWT or Swing with SWT.

SWTFXUtils.toFXImage returns null with SWT 3.121 and later, in JVM mode too : it looks up members of SWT that SWT no longer has. SWTFXUtils.fromFXImage works. On Linux, Glass uses the X11 display of GDK : in a Wayland session, start the application with GDK_BACKEND=x11 (not verified).

On Windows, JavaFX takes the zoom of SWT, the one of the primary monitor when SWT starts : FXCanvas sets it for every JavaFX window (glass.win.uiScale, which replaces a -Dglass.win.uiScale option). By default, SWT 3.132 (the version of Quarkus Desktop SWT 0.2.1) rounds it to a multiple of 100 % (125 % and 150 % give 100 %, 175 % gives 200 %) : at 125 % or 150 %, JavaFX draws at 100 %, and so does SWT, except its text. For the zoom of the monitor, start the application with -Dswt.autoScale=quarter (the nearest multiple of 25 %) or -Dswt.autoScale=exact, in JVM mode and as a native executable. SWT reads it once, when it creates its Display : setting it in an SwtStartupEvent observer is too late. SWT 3.133 and later scale each monitor by default on Windows, to the nearest multiple of 25 % (not verified with FXCanvas).

The SWT variant of the showcase (-Dswt) shows its pages in an SWT shell through FXCanvas, and compares them in JVM mode and as a native executable.

javafx-swt

OpenJFX does not publish javafx-swt on Maven Central : it ships it in the JavaFX SDK (lib/javafx-swt.jar), and nests it in every org.openjfx:javafx-graphics platform jar, where it cannot be used. Install it once in your Maven repository, with the JavaFX version of the application (it uses JavaFX internals), and keep the artifactId javafx-swt, which gives its automatic module name javafx.swt :

mvn dependency:unpack "-Dartifact=org.openjfx:javafx-graphics:25.0.4:jar:linux" "-Dmdep.unpack.includes=javafx-swt.jar" "-DoutputDirectory=target/javafx-swt"
mvn install:install-file "-Dfile=target/javafx-swt/javafx-swt.jar" "-DgroupId=org.openjfx" "-DartifactId=javafx-swt" "-Dversion=25.0.4" "-Dpackaging=jar"

Any platform classifier gives the same classes, and -Dfile=<JavaFX SDK>/lib/javafx-swt.jar works too. Quarkus FX finds javafx-swt whatever its coordinates, for example a Gradle files(…​) dependency (see the installation of Quarkus Desktop SWT for the SWT jar with Gradle). Declare the other JavaFX modules of the application in that version too (Quarkus FX depends on an earlier one) : javafx-swt 25 needs Java 23 or later, as JavaFX 25.

Properties

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

JavaFX UI behavior when Quarkus restarts in dev mode (for example, by pressing s). RECREATE rebuilds the UI using the new CDI container and FXML resources. PRESERVE retains the original UI without rebinding it to the new CDI container. Stylesheet refresh is configured separately. Restart the dev process after changing this setting.

Environment variable: QUARKUS_FX_HOT_RELOAD_STRATEGY

recreate, preserve

recreate

Root location for fx views. The extension will look for fx views from this root directory.

Environment variable: QUARKUS_FX_VIEWS_ROOT

string

/

Stylesheet live reload strategy.

Environment variable: QUARKUS_FX_STYLESHEET_RELOAD_STRATEGY

always, dev, never

dev

Location for source resources (allowing stylesheet live reload in dev mode)

Environment variable: QUARKUS_FX_SOURCE_RESOURCES

string

src/main/resources/

Location for target resources (where resources files are located after build) In dev mode, if stylesheet reload is activated, app will use stylesheet from sources instead of the ones in target and monitor changes

Environment variable: QUARKUS_FX_TARGET_RESOURCES

string

target/classes/

Native support

Initial native support has been added, to build an executable with GraalVM.

On macOS, native executables need JavaFX 24 or later (JavaFX 24 needs Java 22 or later). With an earlier version, the executable crashes when the JavaFX toolkit exits, for example when the last window is closed ("Fatal error: Must either be at a safepoint or in native mode"): up to JavaFX 23, JavaFX detaches the first thread of the process from the VM when it exits. Quarkus FX logs a warning when it builds a macOS native executable with an earlier version.

Quarkus FX depends on an earlier JavaFX version: declare the JavaFX modules of the application with a later version, for example :

<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>25.0.4</version>
</dependency>

javafx-fxml brings javafx-controls, javafx-graphics and javafx-base in the same version: declare the other JavaFX modules the application uses (for example javafx-media or javafx-web) with that version too.

Generate reachability metadata

The extension automatically configures reflection and runtime initialization of the JavaFX components, for supported operating systems (Windows, Mac & Linux). The JavaFX features relying on AWT or Swing also need Quarkus Desktop, see AWT, Swing interop and printing, and so does JavaFX embedded in SWT, see JavaFX embedded in SWT.

You will also need to build your application reachability metadata. To do so, you can :

Run the application using GraalVM to collect reachability metadata:

Example on Windows
%GRAALVM_HOME%\bin\java.exe -agentlib:native-image-agent=config-output-dir=tracing-output -jar quarkus-run.jar

Try to load all UI components in the application and do all possible interactions. Upon exit, tracing-output/reachability-metadata.json will be generated.

Copy tracing-output/reachability-metadata.json to src/main/resources/META-INF/native-image/io.quarkiverse.fx/quarkus-fx/reachability-metadata.json

AWT, Swing interop and printing

Some JavaFX features rely on AWT or Swing:

  • printing (javafx.print, WebEngine.print, HTMLEditor.print),

  • the image formats JavaFX does not decode itself (for example TIFF), loaded with ImageIO (JavaFX 24 and later), and SwingFXUtils,

  • the J2D rendering pipeline (-Dprism.order=j2d),

  • the system beep of WebView, for example when cutting or copying without a selection (without Quarkus Desktop, it crashes the native executable),

  • SwingNode (Swing components in a JavaFX scene) and JFXPanel (a JavaFX scene in a Swing application), which also need org.openjfx:javafx-swing.

AWT and Swing do not work in a native executable on their own: add the Quarkus Desktop extension your application needs. Quarkus FX detects it and configures these JavaFX features for the native executable. Quarkus Desktop is available on Maven Central (version 0.1.0 and later), and needs Quarkus 3.40 or later, for example :

<dependency>
    <groupId>io.quarkiverse.desktop</groupId>
    <artifactId>quarkus-desktop-swing</artifactId>
    <version>0.1.0</version>
</dependency>
Dependency JavaFX features working in native executables

io.quarkiverse.desktop:quarkus-desktop-awt

printing, the ImageIO image formats, SwingFXUtils, the J2D pipeline and the system beep of WebView

io.quarkiverse.desktop:quarkus-desktop-swing (includes quarkus-desktop-awt)

the above, plus SwingNode and JFXPanel when org.openjfx:javafx-swing is a dependency

Without Quarkus Desktop, these features only work in JVM mode. Quarkus Desktop builds native executables for Windows, Linux and macOS (macOS: Quarkus 4.0.0.Beta1 or later, whose quarkus-awt supports macOS, see quarkusio/quarkus#56979, and GraalVM 25.1 or later on Apple silicon). On macOS, AWT runs embedded in the JavaFX application, as in JVM mode: the application handlers of java.awt.Desktop (About, Preferences, Quit, open files) are not called and the apple.awt.application.* properties have no effect; use the JavaFX APIs. Do not block the startup thread waiting for AWT work done on another thread.

A Swing application embedding JavaFX with JFXPanel starts the JavaFX runtime itself: provide your own @QuarkusMain, otherwise Quarkus FX launches a JavaFX application too. FxApplicationStartupEvent, FxPostStartupEvent, FxStartupLatch, @RunOnFxThread and HostServices injection rely on the JavaFX Application launched by Quarkus FX, and are not available in that case.

WebView

The WebKit library of JavaFX links libjvm, the library of the JVM, without using it, on macOS, on Linux x86_64, and on Linux aarch64 with JavaFX 24: a native executable has no libjvm. When the native executable of an application depending on javafx-web starts on macOS or Linux, Quarkus FX writes an empty stand-in in the directory where JavaFX extracts its native libraries (~/.openjfx/cache/<JavaFX version>/<architecture>, or the javafx.cachedir system property), replacing a different file of that name: WebView works as in JVM mode.

  • On macOS, WebKit finds libjvm.dylib next to it. In the directory of the executable, where GraalVM 25.1 and later writes its own libjvm.dylib for AWT, an existing libjvm.dylib is kept.

  • On Linux, Quarkus FX loads libjvm.so, or a temporary copy when that directory cannot be written. With Quarkus Desktop, the stand-in also satisfies the libjvm.so dependency of the AWT library, in place of the libjvm.so that GraalVM writes next to the executable. This is equivalent: the libjvm.so of GraalVM is empty too, and the native executable itself provides the JVM functions that AWT uses.

Quarkus FX reads javafx.cachedir when the executable starts: set it on the command line (-Djavafx.cachedir=…​), not with System::setProperty.

Class path resource URLs

In a native executable, class path resources have resource: URLs (Class::getResource) instead of jar: or file: URLs. Quarkus FX percent-encodes the resource names in these URLs, and decodes them when they are opened, as the JDK does for jar: URLs: resources whose names contain spaces, non-ASCII characters or other characters that a URI does not accept (stylesheets, CSS url(), @import and @font-face, Media and AudioClip, WebView pages and their resources, URL::toURI) work as in JVM mode. Like the JDK, it encodes non-ASCII characters as UTF-8: the URL of images/café.png ends with images/caf%C3%A9.png. This applies to GraalVM 25.0 (Community, Oracle GraalVM, Mandrel) and GraalVM Community 25.2 to 25.4; later GraalVM versions encode these URLs themselves.

Do not store resource: URLs: they depend on the build (resource:/3!/images/logo.png).

A WebView page loaded from a class path URL (WebEngine::load) has the same origin as in JVM mode, where its URL is a jar:file: URL: XMLHttpRequest and fetch of its resources, module scripts, workers, local storage, history.pushState, canvas getImageData and iframe documents work as in JVM mode. WebKit gets it as a jar:file:/resource:/…​ URL, which JavaScript and the DOM see (document.URL, location, the URLs of its links and images, getDocumentURI()); WebEngine::getLocation and the history return the resource: URL. To open a URL taken from the page in Java, replace its jar:file:/resource:/ prefix with resource:/. A page that navigates to a resource: URL itself (an absolute link, location.href) gets an opaque origin: use relative URLs between class path pages. As in JVM mode, a missing class path page or resource (including one not included in the native executable) fails to load ("File not found"), instead of loading as an empty page, script, stylesheet or response.

macOS versions and window style

A macOS native executable declares the minimum macOS version and the SDK version of the java launcher of the JDK that builds it, as a JVM application does, so that its windows have the style of JVM mode. macOS chooses the style of the windows of an executable from its SDK version, and the java launcher of current JDKs is linked with the macOS 14 SDK: windows have the style of macOS 15 and before (a centered title).

With Quarkus Desktop (quarkus-desktop-awt or quarkus-desktop-swt), Quarkus Desktop declares these versions (quarkus.desktop.awt.macos.jdk-build-version, quarkus.desktop.swt.macos.jdk-build-version). To check the versions of an executable: otool -l <executable> | grep -A4 LC_BUILD_VERSION.

Build executable

Environment variable GRAALVM_HOME has to be set, pointing to your GraalVM installation.

In order to build the application as a native executable, you can run the following maven command :

mvn install -Dnative
Please report any found bug by opening an issue at https://github.com/quarkiverse/quarkus-fx/issues