0

I am trying to run a sample application that integrates JavaFX and Spring Boot, working with Intellij and Gradle. To achieve the integration I am testing Gluon Ignite using their Spring Boot Sample from the Wiki.

I run the application as a Spring Boot application (bootRun task in Gradle) but get the error

JavaFX runtime components are missing, and are required to run this application

which clearly says that JavaFX components are not in the module path. This error does not happen if I run the application as a JVM app (task Run), but obviously the Spring Boot part does not work (tested on a more complex version using Spring Boot WebSocket components).

Which is the right way to have the Spring Boot app to run with the JavaFX runtime components correctly added to the module path?

In a normal run the org.javamodularity.moduleplugin takes care of everything, but in the boot run it does not work apparently.

Here below the relevant elements.

build.gradle

plugins {
    id 'application'
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.javamodularity.moduleplugin' version '1.8.12'
    id 'org.openjfx.javafxplugin' version '0.0.10'
}

javafx {
    modules = ['javafx.controls','javafx.fxml']
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.gluonhq:ignite-spring:1.1.0'
    implementation 'javax.inject:javax.inject:1'

    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    implementation 'org.webjars:webjars-locator-core'
    implementation 'org.webjars:stomp-websocket:2.3.3'
    implementation 'org.webjars:bootstrap:3.3.7'
    implementation 'org.webjars:jquery:3.1.1-1'
}

application {
    mainModule = "com.my.ignite"
    mainClass = "com.my.ignite.SpringBootApp"
}

module-info

module com.my.ignite {

  requires javafx.controls;
  requires javafx.fxml;
  requires javafx.graphics;
  requires javafx.base;

  requires spring.messaging;
  requires spring.web;
  requires spring.context;
  requires spring.boot.autoconfigure;
  requires spring.boot;
  requires spring.websocket;
  requires spring.beans;

  requires javax.inject;

  requires ignite.spring;

  exports com.my.ignite;
  opens com.my.ignite;
}

The application code:

package com.my.ignite;

import com.gluonhq.ignite.spring.SpringContext;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import java.io.IOException;
import java.util.Arrays;

@SpringBootApplication
@ComponentScan("com.gluonhq.ignite.spring")
public class SpringBootApp extends Application implements ExampleApp {

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

  @Autowired
  private FXMLLoader fxmlLoader;

  // Originally in the sample code:
  //  private final SpringContext context = new SpringContext(this);

  // Modified (see "Basic Spring Sample" code):
  private final SpringContext context = new SpringContext(this,
      () -> Arrays.asList(SpringBootApp.class.getPackage().getName()));

  @Override
  public void start(Stage primaryStage) throws IOException {
    // Originally in the sample code:
    // context.init(() -> SpringApplication.run(SpringBootApp.class));

    // Modified:
    context.init();

    fxmlLoader.setLocation(getViewLocation());
    Parent view = fxmlLoader.load();
    primaryStage.setTitle("Spring Boot Example");
    primaryStage.setScene(new Scene(view));
    primaryStage.show();
  }

}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
CT95
  • 107
  • 1
  • 11
  • The [spring boot gradle plugin documentation states](https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#running-your-application): *"The task is automatically configured to use the runtime classpath of the main source set."*. It isn't set up to run a modular application with modules on the module path. Perhaps you could [manually add options to the javaexec](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html) to set the module path and add modules (I don't know). – jewelsea Feb 24 '23 at 23:20
  • Despite being initially advertised to do so, Spring 6 and SpringBoot 3 currently [don’t fully support Java modules](https://www.infoq.com/news/2022/10/spring-boot-3-jax-london/). Though you might be able to get a modular project to work with them, there may not be a lot of documentation on how to do it, generated initializer projects won’t properly define default `module-info.java` files, there might be some work required to make the app function as expected, and benefits of modularity might be limited (e.g. jlink may not work on the app). – jewelsea Feb 25 '23 at 05:21
  • Thanks for the hint. In practice this is excluding the option of integrating JavaFX and Spring Boot as my application is modular and I have no intention to step back to non-modular or waste a lot of time to figure out how make it work. I will find another way to have JavaFX to talk with my SpringBoot client, any hint is welcome. – CT95 Feb 25 '23 at 17:22
  • Write your SpringBoot client as a Service with an API that can be called from plain old Java. Architect your JavaFX application as some kind of MVC, and call the SpringBoot client service from the Model. Your SpringBoot service takes/returns "domain objects" which the code in the Model integrates with the Presentation Model you need to support your View. – DaveB Feb 25 '23 at 17:38
  • Solved the [original problem](https://stackoverflow.com/questions/75550700/alternative-way-to-integrate-javafx-with-a-stomp-websocket-client-preferably-sp/75552641#75552641) using a standalone Spring boot Websocket Client working as message broker. The JavaFX app launches the broker and communicates with it over a TCP socket. The broker sends the messages to the websocket server and dispatches the response to the JavaFX app. Maybe not the best solution for elegance and performance, but easy to implement, minimal dependencies, uses Spring boot and keeps the JavaFX app modular and independent. – CT95 Feb 25 '23 at 21:08

0 Answers0