Spring Framework
Logging
Commons Logging
Java
Spring-JCL

Standard Commons Logging discovery in action with spring-jcl

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

spring-jcl is Spring's replacement bridge for the Commons Logging API. It keeps the familiar org.apache.commons.logging.Log and LogFactory surface, but the discovery behavior is optimized for Spring's supported logging backends rather than following the older full Commons Logging discovery story.

What Standard Commons Logging Discovery Tries to Do

Classic Commons Logging was designed as a logging facade. Application code calls the Commons Logging API, and the library tries to discover a concrete logging implementation at runtime by inspecting the classpath.

At the call site, the code is simple:

java
1import org.apache.commons.logging.Log;
2import org.apache.commons.logging.LogFactory;
3
4public class DemoService {
5    private static final Log logger = LogFactory.getLog(DemoService.class);
6
7    public void run() {
8        logger.info("Service started");
9    }
10}

The idea is that the application code does not commit directly to Log4J, SLF4J, or java.util.logging. Instead, discovery selects a backend based on what jars are present.

That flexibility was useful, but the original Commons Logging implementation was also known for classloader surprises and bridge conflicts in some container environments. This is exactly the kind of problem Spring wanted to smooth out.

What spring-jcl Changes

Spring ships spring-jcl as its own Commons Logging bridge. According to the Spring Framework API docs, it is a custom bridge with special support for Log4J 2, SLF4J, and java.util.logging, and it uses a minimized LogFactory implementation tailored for Spring.

In practical terms, that means:

  • Spring code can keep using the Commons Logging API
  • backend detection is simpler and more predictable
  • common Spring logging setups work without adding extra JCL bridge jars

So when people talk about "Commons Logging discovery with Spring," the important nuance is that modern Spring applications usually are not using the original commons-logging artifact directly. They are using Spring's bridge layer instead.

How It Behaves in a Spring Application

In a typical Spring Boot application, you usually do not configure spring-jcl directly. It arrives transitively with the Spring framework jars, and the effective backend is then determined by the logging implementation on the classpath.

If SLF4J and Logback are present through Spring Boot's default logging starter, calls made through LogFactory.getLog will flow into that stack. If your application is using Log4J 2, the bridge resolves there instead. If neither SLF4J nor Log4J 2 is available, the fallback is typically java.util.logging.

The code inside your component stays unchanged:

java
1import org.apache.commons.logging.Log;
2import org.apache.commons.logging.LogFactory;
3import org.springframework.stereotype.Service;
4
5@Service
6public class BillingService {
7    private static final Log logger = LogFactory.getLog(BillingService.class);
8
9    public void charge(String userId) {
10        logger.debug("Charging user " + userId);
11    }
12}

That is the main value of the bridge: library and framework code can stay on a stable API while deployment chooses the concrete backend.

The Real Risk Is Bridge Conflicts

Most logging problems around spring-jcl are not about missing logger creation. They are about conflicting bridges on the classpath.

For example, this is a safe, explicit way to replace Spring Boot's default logging starter with Log4J 2:

xml
1<dependencies>
2  <dependency>
3    <groupId>org.springframework.boot</groupId>
4    <artifactId>spring-boot-starter-web</artifactId>
5    <exclusions>
6      <exclusion>
7        <groupId>org.springframework.boot</groupId>
8        <artifactId>spring-boot-starter-logging</artifactId>
9      </exclusion>
10    </exclusions>
11  </dependency>
12
13  <dependency>
14    <groupId>org.springframework.boot</groupId>
15    <artifactId>spring-boot-starter-log4j2</artifactId>
16  </dependency>
17</dependencies>

What you should not do is pile on multiple incompatible bridge layers and hope discovery sorts it out. Mixing spring-jcl, the original commons-logging, and jcl-over-slf4j without understanding the resolution chain can create duplicate bindings or cycles.

As a rule:

  • keep one Commons Logging bridge strategy
  • keep one final logging backend
  • inspect the dependency tree when logs behave strangely

How to Debug Discovery Issues

When the wrong backend seems active, inspect the runtime classpath first rather than editing application code. In Maven, this is a good first step:

bash
mvn dependency:tree | grep -Ei "spring-jcl|commons-logging|slf4j|log4j"

That usually reveals duplicate bridges quickly. If both commons-logging and spring-jcl are present, or if you see multiple SLF4J bindings, clean that up before debugging anything else.

Also remember that Spring's own docs position spring-jcl mainly as a framework bridge. For application code, direct use of SLF4J or another chosen logging API is often clearer. The bridge is most valuable because Spring itself and Spring-based libraries can log consistently without forcing one backend on every application.

Common Pitfalls

The most common mistake is assuming spring-jcl is the same implementation as the original Commons Logging jar. It is not. The API is familiar, but the discovery and bridge behavior are Spring-specific.

Another common problem is bridge stacking. Adding jcl-over-slf4j on top of spring-jcl without excluding something usually creates more confusion, not less. Developers also sometimes debug the wrong layer entirely. If logs are missing, inspect the dependency graph and backend bindings before changing logger calls in the source.

Summary

  • 'spring-jcl keeps the Commons Logging API but uses Spring's own bridge and backend detection behavior.'
  • In modern Spring apps, logging usually flows from spring-jcl into SLF4J, Log4J 2, or java.util.logging.
  • The main operational risk is conflicting bridge jars on the classpath.
  • Use dependency inspection to debug discovery issues before touching application code.
  • For application-facing code, direct SLF4J usage is often clearer, while spring-jcl remains valuable for framework integration.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.