Logback not working in a JavaFX image built by JLink

I have a JavaFX application built with Maven which uses logback-classic as a SLF4J provider. I am using javafx-maven-plugin to run the app and to build a jlink image.

When I run the app using mvn javafx:run, the logging is fine.

When I build an image using mvn javafx:jlink and run that image, I get the error:

SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.

It seems like logback-classic is not being properly included in the image.

I have tried to manually copy logback-classic-1.5.7.jar and logback-core-1.5.7.jar into target/image/lib but it had no effect.

How can I resolve this?

1

When linking, the module resolution algorithm starts with a set of root modules and then recursively enumerates their requires directives until all required modules are found (though it appears to skip modules that are only ever requires static). As far as I can tell, the root modules are specified by the --add-modules argument and each --launcher argument (if any). Once these modules are resolved, the algorithm will perform another recursive search for any modules which provides a service any resolved module uses, but only if --bind-services is passed. If a module is not resolved (because it is not a root module, is not directly or indirectly required by a root module, and doesn’t provide a service), then it will not be included in the runtime image.

The ch.qos.logback.classic module is a so-called “provider module”. That means its primary purpose is to provide a service that is used by another module and is rarely directly or indirectly required. Thus, it will not be resolved when creating the runtime image with jlink unless you either pass --bind-services or explicitly include it via --add-modules.

The javafx-maven-plugin plugin for Maven unfortunately does not seem to provide a way to specify --add-modules. Though you can tell it to pass --bind-services with:

<bindServices>true</bindServices>

Though to get it to include ch.qos.logback.classic on the module-path when executing javafx:jlink, it seems you also have to set:

<runtimePathOption>MODULEPATH</runtimePathOption>

However, --bind-services will include all provider modules of any service used by each resolved module. This can lead to significantly more modules from the JDK being included than necessary. You can control this somewhat by passing an appropriate --limit-modules argument, but again the plugin doesn’t seem to have a way to pass that argument. Plus, using --limit-modules seems more like a workaround in this case than a genuine solution.

In short, you basically have the following options:

  1. Configure the javafx-maven-plugin to pass --bind-services and live with the larger-than-necessary runtime image.

  2. Add a requires ch.qos.logback.classic; directive to your application’s module-info descriptor.

    Note that requiring a provider module should typically be avoided. Doing so can make it harder to swap providers. However, I suspect requiring ch.qos.logback.classic won’t cause significant issues in your case and is likely the easiest and most efficient solution.

  3. Use a different Maven plugin for creating runtime images, one that gives you more control over the arguments passed to jlink.


Bind Services Example

Here is an example configuring javafx-maven-plugin to pass --bind-services.

Versions

  • Maven 3.9.9

  • Java 23

  • JavaFX 23.0.1

  • SLF4J 2.0.16

  • Logback Classic 1.5.12

  • javafx-maven-plugin 0.0.8

Source Code

module-info.java

module app {
  requires javafx.controls;
  requires org.slf4j;

  exports com.example.app to
      javafx.graphics;
}

Main.java

package com.example.app;

import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main extends Application {

  private static final Logger LOG = LoggerFactory.getLogger(Main.class);

  @Override
  public void init() {
    var modules = ModuleLayer.boot()
        .modules()
        .stream()
        .map(Module::getName)
        .sorted()
        .collect(Collectors.joining("n   ", "n   ", ""));
    LOG.info("Resolved modules: {}", modules);
  }

  @Override
  public void start(Stage primaryStage) {
    LOG.info("Application start.");

    var root = new StackPane(new Label("Hello, World!"));
    primaryStage.setScene(new Scene(root, 500, 300));
    primaryStage.setTitle("Example");
    primaryStage.show();
  }

  @Override
  public void stop() {
    LOG.info("Application stop.");
  }
}

POM

<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>

  <groupId>com.example</groupId>
  <artifactId>jfx-slf4j-test</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>23</maven.compiler.release>

    <slf4jVersion>2.0.16</slf4jVersion>
    <logbackVersion>1.5.12</logbackVersion>
    <javafxVersion>23.0.1</javafxVersion>
  </properties>

  <build>

    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.13.0</version>
        </plugin>
      </plugins>
    </pluginManagement>

    <plugins>
      <plugin>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-maven-plugin</artifactId>
        <version>0.0.8</version>
        <configuration>
          <runtimePathOption>MODULEPATH</runtimePathOption>
          <mainClass>app/com.example.app.Main</mainClass>
          <launcher>example</launcher>
          <bindServices>true</bindServices>
        </configuration>
      </plugin>
    </plugins>

  </build>

  <dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4jVersion}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logbackVersion}</version>
    </dependency>

    <dependency>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-controls</artifactId>
      <version>${javafxVersion}</version>
    </dependency>
  </dependencies>

</project>

Project Structure

<PROJECT-ROOT>
|   pom.xml
|
---src
    ---main
        ---java
            |   module-info.java
            |
            ---com
                ---example
                    ---app
                            Main.java

Output

From executing mvn javafx:jlink:

... > mvn javafx:jlink

[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:jfx-slf4j-test >---------------------
[INFO] Building jfx-slf4j-test 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> javafx:0.0.8:jlink (default-cli) > process-classes @ jfx-slf4j-test >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ jfx-slf4j-test ---
[INFO] skip non existing resourceDirectory C:...jfx-slf4j-testsrcmainresources
[INFO] 
[INFO] --- compiler:3.13.0:compile (default-compile) @ jfx-slf4j-test ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 2 source files with javac [debug release 23 module-path] to targetclasses
[INFO] 
[INFO] <<< javafx:0.0.8:jlink (default-cli) < process-classes @ jfx-slf4j-test <<<
[INFO] 
[INFO] 
[INFO] --- javafx:0.0.8:jlink (default-cli) @ jfx-slf4j-test ---
Warning: The 0 argument for --compress is deprecated and may be removed in a future release
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.956 s
[INFO] Finished at: 2024-10-27T13:40:10-06:00
[INFO] ------------------------------------------------------------------------

From executing the runtime image via the launcher script:

... > ./target/image/bin/example

13:42:29.164 [JavaFX-Launcher] INFO com.example.app.Main -- Resolved modules: 
   app
   ch.qos.logback.classic
   ch.qos.logback.core
   java.base
   java.compiler
   java.datatransfer
   java.desktop
   java.logging
   java.management
   java.management.rmi
   java.naming
   java.prefs
   java.rmi
   java.security.jgss
   java.security.sasl
   java.smartcardio
   java.xml
   java.xml.crypto
   javafx.base
   javafx.controls
   javafx.graphics
   jdk.accessibility
   jdk.attach
   jdk.charsets
   jdk.compiler
   jdk.crypto.cryptoki
   jdk.crypto.mscapi
   jdk.editpad
   jdk.internal.ed
   jdk.internal.jvmstat
   jdk.internal.le
   jdk.internal.md
   jdk.internal.opt
   jdk.jartool
   jdk.javadoc
   jdk.jdeps
   jdk.jdi
   jdk.jdwp.agent
   jdk.jfr
   jdk.jlink
   jdk.jpackage
   jdk.jshell
   jdk.jstatd
   jdk.localedata
   jdk.management
   jdk.management.jfr
   jdk.naming.dns
   jdk.naming.rmi
   jdk.security.auth
   jdk.security.jgss
   jdk.unsupported
   jdk.unsupported.desktop
   jdk.zipfs
   org.slf4j
13:42:29.182 [JavaFX Application Thread] INFO com.example.app.Main -- Application start.
13:42:31.689 [JavaFX Application Thread] INFO com.example.app.Main -- Application stop.

(Note how this approach leads to many more modules in the runtime image than needed)

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật