The "SLF4JServiceProvider not found" issue

I was working on a Desktop app and encountered a log error as shown below:

SLF4J(E): A service provider failed to instantiate:
org.slf4j.spi.SLF4JServiceProvider: Provider org.apache.logging.slf4j.SLF4JServiceProvider not found
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.

The app is built with Kotlin Multiplatform. Underneath, it is a regular Java app. This log error was a bit of a surprise because everything that should have been included was already included, namely, log4j-api, log4j-slf4j2-impl, and slf4j-api. The versions were correct.

In this blog, I'll walkthrough how to investigate such an issue.

The first insight after googling around is that org.slf4j.spi.SLF4JServiceProvider should have been provided by the library: log4j-slf4j2-impl. That's where an SLF4J provider is provided to log through Log4j.

We need to confirm this by getting the JAR file of log4j-slf4j2-impl from my built binary. In Mac, I can right-click on the app binary and select "Show Package Contents". Then, I can look for that JAR file.

Since a JAR file is actually a ZIP file, I can run unzip to see the contents inside, and there was the first clue. The JAR file contained a bunch of *.class but none of them was org.slf4j.spi.SLF4JServiceProvider.

I then looked up the same JAR on Maven Central and the same JAR in Gradle's cache. I unzipped them and found that both of them contained org.slf4j.spi.SLF4JServiceProvider. That was surprising!

There is a certain process that packages the binary and deletes a bunch of classes. If you work with a Java Desktop app before, you might know this. It's Proguard, which is equivalent to a tree shaking in the JS world. It makes the release binary much smaller by deleting classes that aren't used. Or, to be specific, it deletes classes that aren't used at the compile time.

Since SLF4J loads its provider dynamically using ServiceLoader, Proguard isn't aware that org.slf4j.spi.SLF4JServiceProvider was actually used. The solution is to tell Proguard to include all classes from log4j-slf4j2-impl. Adding the line below to proguard.pro would make that happen:

-keep class org.apache.logging.slf4j.** { *; }

This took me an hour to figure out. I was spending too much time investigating a potential dependency conflict, which didn't exist. I should've investigated it based on the error message and verified that things were true from the bottom-up instead.

Subscribe to tanin

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe