0

I'm building a Spring Boot 3.4.5 application with a JavaFX GUI. What I'm essentially trying to do is start the app with GUI which has the buttons to start/restart/stop the Spring Boot server. The app runs fine in development mode inside my IDE, but when I build a fat jar and try to run it, I get the following error, Below is the custom log that i print inside my JavaFX based GUI app:

[22:48:10] ▶ Starting server...
[22:48:10] ❌ Failed to start server: Unable to instantiate factory class [org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer] for factory type [org.springframework.context.ApplicationContextInitializer]
[22:48:10] 📋 java.lang.IllegalArgumentException: Unable to instantiate factory class [org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer] for factory type [org.springframework.context.ApplicationContextInitializer]
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$throwing$0(SpringFactoriesLoader.java:641)
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$handleMessage$3(SpringFactoriesLoader.java:665)
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.instantiateFactory(SpringFactoriesLoader.java:227)
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:202)
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:158)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:466)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:462)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:278)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:257)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362)
[22:48:10] 📋 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351)
[22:48:10] 📋 at com.novastream.config.ServerControlGUI.lambda$startServer$16(ServerControlGUI.java:403)
[22:48:10] 📋 at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1848)
[22:48:10] 📋 at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1840)
[22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
[22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458)
[22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034)
[22:48:10] 📋 at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)
[22:48:10] 📋 Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
[22:48:10] 📋 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
[22:48:10] 📋 at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
[22:48:10] 📋 at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528)
[22:48:10] 📋 at java.base/java.lang.Class.forName0(Native Method)
[22:48:10] 📋 at java.base/java.lang.Class.forName(Class.java:578)
[22:48:10] 📋 at java.base/java.lang.Class.forName(Class.java:557)
[22:48:10] 📋 at org.springframework.util.ClassUtils.forName(ClassUtils.java:321)
[22:48:10] 📋 at org.springframework.core.io.support.SpringFactoriesLoader.instantiateFactory(SpringFactoriesLoader.java:220)
[22:48:10] 📋 ... 15 more

This is what my GUI looks like and is 100% working in dev mode

Above image shows what my GUI looks like and is 100% working in dev mode.

enter image description here

Above in the 2nd image is the same GUI but when the app is run using built JAR (this error pops up when the server start button is pressed).

My main entry point looks like this:

package com.novastream;

import com.novastream.config.MediaConfig;
import com.novastream.config.ServerControlGUI;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
@EnableConfigurationProperties(MediaConfig.class)
public class NovastreamBackendApplication {

  public static void main(String[] args) {
    System.setProperty("java.awt.headless", "false");
    ServerControlGUI.launch(ServerControlGUI.class);
  }
}

My pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.5</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.novastream</groupId>
    <artifactId>novastream-backend</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>novastream-backend</name>
    <description>NovaStream Backend</description>
    <url />
    <licenses>
        <license />
    </licenses>
    <developers>
        <developer />
    </developers>
    <scm>
        <connection />
        <developerConnection />
        <tag />
        <url />
    </scm>
    <properties>
        <java.version>23</java.version>
        <javafx.version>21</javafx.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>3.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-graphics</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.4.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

A short version of my ServerControlGUI.java (My class for JavaFX based GUI):

@Component
public class ServerControlGUI extends Application {
@Override
  public void start(Stage primaryStage) {
    primaryStage.initStyle(StageStyle.TRANSPARENT);

    DropShadow shadow = new DropShadow(20, Color.BLACK);
    shadow.setOffsetX(0);
    shadow.setOffsetY(10);

    VBox root = createMainLayout(primaryStage);
    root.setEffect(shadow);
    root.getStyleClass().add("main-container");

    Rectangle clip = new Rectangle(800, 600);
    clip.setArcWidth(16);
    clip.setArcHeight(16);
    root.setClip(clip);

    Scene scene = new Scene(root, 800, 600);
    scene.setFill(Color.TRANSPARENT);
    scene
      .getStylesheets()
      .add(getClass().getResource("/static/gui.css").toExternalForm());

    primaryStage.setScene(scene);
    primaryStage.setTitle("NovaStream");
    primaryStage.show();
    animateStartup(primaryStage);

    primaryStage.setOnCloseRequest(e -> {
      stopServer();
      Platform.exit();
      System.exit(0);
    });

    setupSystemStreamRedirection();
  }

  private VBox createControlPanel() {
    VBox panel = new VBox();
    panel.getStyleClass().add("control-panel");

    Label title = new Label("Server Controls");
    title.getStyleClass().add("control-title");

    HBox controls = new HBox();
    controls.getStyleClass().add("controls-container");

    startRestartBtn =
      createActionButton("▶ Start Server", "start-button", this::startServer);
    stopBtn =
      createActionButton("⏹ Stop Server", "stop-button", this::stopServer);
    clearLogsBtn =
      createActionButton("🗑 Clear Logs", "clear-button", this::clearLogs);

    stopBtn.setDisable(true);
    clearLogsBtn.setDisable(true);

    controls.getChildren().addAll(startRestartBtn, stopBtn, clearLogsBtn);
    panel.getChildren().addAll(title, controls);

    return panel;
  }
  private void startServer() {
    if (loadingIndicator.isVisible() || serverRunning) return;

    Platform.runLater(() -> {
      loadingIndicator.setVisible(true);
      updateStatusLabel("Starting...", "status-starting");
      startRestartBtn.setDisable(true);
      stopBtn.setDisable(true);
    });

    CompletableFuture.runAsync(() -> {
      try {
        appendLog("▶ Starting server...");

        springContext =
          SpringApplication.run(NovastreamBackendApplication.class);
        serverRunning = true;
        startTime = LocalDateTime.now();

        Platform.runLater(() -> {
          loadingIndicator.setVisible(false);
          updateStatusLabel("Running", "status-running");
          startRestartBtn.setDisable(false);
          stopBtn.setDisable(false);

          updateStartRestartButton();
          startUptimeTimer();
        });
        appendLog("✅ Server started successfully");
        appendLog("🌐 Server running on http://localhost:8080");
      } catch (Exception e) {
        Platform.runLater(() -> {
          loadingIndicator.setVisible(false);
          updateStatusLabel("Failed", "status-failed");
          startRestartBtn.setDisable(false);
        });
        appendLog("❌ Failed to start server: " + e.getMessage());
        e.printStackTrace();
      }
    });
  }
}

The buttons toggle the server status.

So I basically need help creating the JAR, resolving this issue, as I'm trying to make an exe using that JAR using Launch4J later.

1

1 Answer 1

0

Your current approach is not supported

Repackaging JavaFX into SpringBoot nested jar using the SpringBoot Maven plugin's repackage goal is not a supported configuration. JavaFX is only supported when loaded as a module and not from the classpath. It may be possible to get it to work despite this (I don't know and did not try).

Perhaps in the future, Spring and the JavaFX ecosystem will support nested modules in a single file, but as of Spring 3.5 and Java 24, neither do.

Alternative 1: Package the application using jpackage

An alternate approach is to package the application using jpackage as documented in:

This will create a native installer which you can be used to install your application on a machine.

Alternative 2: Use a JRE that includes JavaFX

You may choose to rely on a java runtime that includes JavaFX (e.g. Azul Zulu "JRE FX", Bellsoft Liberica "Full JRE", Openlogic JRE). Or you can build a custom runtime, following the instruction section "Custom JDK+JavaFX image".

Then in your pom.xml set the scope of JavaFX dependencies as provided.

As your app is non-modular it can access the JavaFX as modules from a compliant runtime using the switch on the java command: --add-modules javafx.controls,javafx.fxml (comma separating the required JavaFX modules as needed).

I didn't try it, but this approach will likely work with the Spring Boot repackage goal. The approach doesn't repackage the JavaFX modules and will allow both Spring Boot and JavaFX to work in supported configurations.

Alternative 3: Create a native executable using warp-packer

I didn't try this with a SpringBoot application, but it might work.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.