Skip to content

GraalVM native-image build fails with slf4j-jdk-platform-logging - an issue due to use of System.getLogger() #264

@rbygrave

Description

@rbygrave

avaje-jex uses System.getLogger("io.avaje.jex") to log messages, if we want System.Logger to get redirected to slf4j (to use whatever logger we setup for slf4j) then we'd look to add the dependency:

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk-platform-logging</artifactId>
      <version>2.0.17</version>
    </dependency>

... and this works well when running normally via the JVM, but when we compile to native-image we get:

mvn clean package -P native,mac -DskipTests
...
[INFO] 
[INFO] --- surefire:3.2.5:test (default-test) @ ebean-insight ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- jar:3.4.1:jar (default-jar) @ ebean-insight ---
[INFO] Building jar: /Users/robinbygrave/github/ebean/ebean-insight-server/target/ebean-insight-1.1-RC1.jar
[INFO] 
[INFO] --- native:0.10.6:build (build-native) @ ebean-insight ---
[WARNING] 'native:build' goal is deprecated. Use 'native:compile-no-fork' instead.
[INFO] Found GraalVM installation from JAVA_HOME variable.
[INFO] Downloaded GraalVM reachability metadata repository from file:/Users/robinbygrave/.m2/repository/org/graalvm/buildtools/graalvm-reachability-metadata/0.10.6/graalvm-reachability-metadata-0.10.6-repository.zip
...
========================================================================================================================
GraalVM Native Image: Generating 'ebean-insight' (executable)...
========================================================================================================================
[1/8] Initializing...                                                                                    (3.5s @ 0.15GB)
 Java version: 24+36, vendor version: Oracle GraalVM 24+36.1
 Graal compiler: optimization level: 2, target machine: native, PGO: ML-inferred
 C compiler: cc (apple, arm64, 17.0.0)
 Garbage collector: Serial GC (max heap size: 100.00MB)
 1 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 12.09GB of memory (75.6% of 16.00GB system memory, determined at start)
 - 12 thread(s) (100.0% of 12 available processor(s), determined at start)
[2/8] Performing analysis...  []                                                                         (2.2s @ 0.24GB)
    2,173 reachable types   (55.6% of    3,910 total)
    1,978 reachable fields  (36.4% of    5,428 total)
    9,147 reachable methods (33.0% of   27,713 total)
      790 types,    32 fields, and   155 methods registered for reflection

Fatal error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: An object of type 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' was found in the image heap. This type, however, is marked for initialization at image run time for the following reason: classes are initialized at run time by default.
This is not allowed for correctness reasons: All objects that are stored in the image heap must be initialized at build time.

You now have two options to resolve this:

1) If it is intended that objects of type 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' are persisted in the image heap, add 

    '--initialize-at-build-time=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder'

to the native-image arguments. Note that initializing new types can store additional objects to the heap. It is advised to check the static fields of 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' to see if they are safe for build-time initialization,  and that they do not contain any sensitive data that should not become part of the image.

2) If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder'

to find classes that instantiate these objects. Once you found such a class, you can mark it explicitly for run time initialization with 

    '--initialize-at-run-time=<culprit>'

to prevent the instantiation of the object.

If you are seeing this message after upgrading to a new GraalVM release, this means that some objects ended up in the image heap without their type being marked with --initialize-at-build-time.
To fix this, include '--initialize-at-build-time=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' in your configuration. If the classes do not originate from your code, it is advised to update all library or framework dependencies to the latest version before addressing this error.

The following detailed trace displays from which field in the code the object was reached.
Object was reached by
  reading static field jdk.internal.logger.LazyLoggers.provider
    at jdk.internal.logger.LazyLoggers.accessLoggerFinder(LazyLoggers.java:340)
  parsing method jdk.internal.logger.LazyLoggers.accessLoggerFinder(LazyLoggers.java:340) reachable via the parsing context
    at jdk.internal.logger.LazyLoggers.getLoggerFromFinder(LazyLoggers.java:396)
    at io.avaje.jex.DefaultLifecycle.<clinit>(DefaultLifecycle.java:13)
    at static root method.(Unknown Source)

        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.heap.ImageHeapScanner.onObjectReachable(ImageHeapScanner.java:598)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.heap.SVMImageHeapScanner.onObjectReachable(SVMImageHeapScanner.java:125)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.heap.ImageHeapScanner.lambda$markReachable$0(ImageHeapScanner.java:579)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.executeCommand(CompletionExecutor.java:166)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.lambda$executeService$0(CompletionExecutor.java:152)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1735)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1726)
        at java.base/java.util.concurrent.ForkJoinTask$InterruptibleTask.exec(ForkJoinTask.java:1650)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
        at java.base/java.util.concurrent.ForkJoinPool.externalHelpQuiesce(ForkJoinPool.java:2458)
        at java.base/java.util.concurrent.ForkJoinPool.helpQuiescePool(ForkJoinPool.java:2492)
        at java.base/java.util.concurrent.ForkJoinPool.awaitQuiescence(ForkJoinPool.java:3740)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.complete(CompletionExecutor.java:208)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.PointsToAnalysis.doTypeflow(PointsToAnalysis.java:640)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.PointsToAnalysis.finish(PointsToAnalysis.java:627)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.AbstractAnalysisEngine.runAnalysis(AbstractAnalysisEngine.java:160)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:821)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:567)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:533)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:545)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:732)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.start(NativeImageGeneratorRunner.java:151)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:99)
Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: An object of type 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' was found in the image heap. This type, however, is marked for initialization at image run time for the following reason: classes are initialized at run time by default.
This is not allowed for correctness reasons: All objects that are stored in the image heap must be initialized at build time.

You now have two options to resolve this:

1) If it is intended that objects of type 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' are persisted in the image heap, add 

    '--initialize-at-build-time=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder'

to the native-image arguments. Note that initializing new types can store additional objects to the heap. It is advised to check the static fields of 'org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' to see if they are safe for build-time initialization,  and that they do not contain any sensitive data that should not become part of the image.

2) If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder'

to find classes that instantiate these objects. Once you found such a class, you can mark it explicitly for run time initialization with 

    '--initialize-at-run-time=<culprit>'

to prevent the instantiation of the object.

If you are seeing this message after upgrading to a new GraalVM release, this means that some objects ended up in the image heap without their type being marked with --initialize-at-build-time.
To fix this, include '--initialize-at-build-time=org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder' in your configuration. If the classes do not originate from your code, it is advised to update all library or framework dependencies to the latest version before addressing this error.

The following detailed trace displays from which field in the code the object was reached.
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationFeature.checkImageHeapInstance(ClassInitializationFeature.java:206)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisType.lambda$notifyObjectReachable$0(AnalysisType.java:682)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.ConcurrentLightHashSet.forEach(ConcurrentLightHashSet.java:150)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisType.notifyObjectReachable(AnalysisType.java:682)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.heap.ImageHeapScanner.onObjectReachable(ImageHeapScanner.java:593)
        ... 22 more
------------------------------------------------------------------------------------------------------------------------
                        0.3s (4.5% of total time) in 64 GCs | Peak RSS: 0.63GB | CPU load: 4.82
========================================================================================================================
Failed generating 'ebean-insight' after 6.1s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  10.071 s
[INFO] Finished at: 2025-05-15T22:07:39+12:00
[INFO] ------------------------------------------------------------------------
...

I've had a few attempts to resolve this via --initialize-at-run-time= and --initialize-at-build-time etc. However, I'm tending to think that with native-image the use of System.getLogger() is eagerly evaluated.

There are 2 options I know that workaround this issue:

  1. Add the io.avaje:avaje-applog dependency, and change use of System.getLogger() to AppLog.getLogger(). Then when io.avaje:avaje-applog-slf4j is added, AppLog uses an SLF4J Logger. This extra 1 layer of indirection works for native-image.
  2. Set all the SLF4J logging to Java Util Logging via adding org.slf4j:slf4j-jdk14 dependency. Then all the logging goes to JUL and you customise the logging from there [but then you are dealing with JUL].

What I was just trying to use org.slf4j:slf4j-simple for some basic logging for a native app using Jex and hit this trying to get the avaje-jex log messages included there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions