Deepak Vadgama bio photo

Deepak Vadgama

Software developer, amateur photographer

Email LinkedIn Github Stackoverflow Youtube Subscribe (RSS)

Logging is one of the most fundamental aspects of any language. Unfortunately, Java’s in-built logging mechanism java.util.logging (available since JDK 1.4) was not feature rich nor flexible enough for most use-cases. The community created multiple libraries to fill in this gap.

After more than a decade, the logging ecosystem has become quite complicated with each library using its own logging mechanism. Even standard projects’ dependencies look like this.

[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.5.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] |  |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.24:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.24:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.24:compile

and some conversations make no sense.

Let us try to understand how we got here.

If you are new to Java logging, refer to this article to understand its features.


log4j was most used logging library in the beginning. Once configured, using it in the code was relatively simple.

import org.apache.log4j.Logger;

public class LogExample {

    final static Logger logger = Logger.getLogger(LogExample.class);

    private void run(){"important log statement");


Though, using log4j classes directly in the code results in tight coupling. The solution to reduce this coupling was to introduce Simple Logging Facade for Java aka slf4j; a library which delegated the calls to the underlying logging implementation (eg: log4j or logback) based on runtime binding.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogExample {

    private static Logger logger = LoggerFactory.getLogger(LogExample.class);

    private void run(){"important log statement");


Unfortunately, the community could not standardize on single facade to use. Many Java libraries used different facades (eg: JCL aka Jakarta Commons Logging) or in many cases direct implementations (ignoring the tight coupling problem).

This created a bigger problem. Suppose you want to create a project and log with combination of slf4j and logback. So far so good. But what if you want to import a library say, Guava, and that in turn uses java.util.logging How would your slf4j or logback know about that library’s logging mechanism? Also, it is impractical to be aware of all libraries’ logging implementations and configure them separately in your project.

Thus, a bridge (aka adaptor) was introduced, which would redirect all those logs called via java.util.logging to slf4j.

Problem solved. Now as long as you have the right bridge(s) for all the logging libraries being used (directly and indirectly) in your project, all the logs will be flown/streamed into slf4j and into your final logging implementation.

slf4j has all the bridges you will ever need.

Image Source

The best part of this solution is – all the bridges are bound/configured automatically at runtime. Of course, the problem of which permutation-combination to use will still require including/excluding the right bridges/facades from your pom.xml. Though framework developers do think hard to make it easy for the developers.

Hit me up in the comments for any queries or critiques.