# Setting Up SLF4J with Logback in Spring Boot: Best Practices and Common Pitfalls

> Master SLF4J with Logback in Spring Boot. Learn best practices and common pitfalls for configuring bindings to prevent runtime errors. Avoid dependency conflicts for smooth logging.

- Repository: [Spring/spring-boot](https://github.com/spring-projects/spring-boot)
- Tags: best-practices
- Published: 2026-02-16

---

**Spring Boot automatically configures SLF4J with Logback through the `spring-boot-starter-logging` module, but runtime errors occur when multiple SLF4J bindings exist on the classpath or when conflicting dependencies like `jcl-over-slf4j` are present.**

When setting up SLF4J with Logback in Spring Boot applications, the framework handles most wiring automatically through the `spring-projects/spring-boot` repository. However, understanding the underlying architecture in [`LogbackLoggingSystem.java`](https://github.com/spring-projects/spring-boot/blob/main/LogbackLoggingSystem.java) and the build-time checks in [`CheckClasspathForProhibitedDependencies.java`](https://github.com/spring-projects/spring-boot/blob/main/CheckClasspathForProhibitedDependencies.java) is essential to avoid classpath conflicts and runtime logging errors.

## How Spring Boot Auto-Configures SLF4J and Logback

Spring Boot’s logging infrastructure centers on [`LogbackLoggingSystem.java`](https://github.com/spring-projects/spring-boot/blob/main/LogbackLoggingSystem.java), which implements the `LoggingSystem` interface to bootstrap Logback and integrate it with SLF4J. When you include `spring-boot-starter-logging` in your build, Spring Boot automatically detects `LogbackLoggingSystem` on the classpath and initializes it during application startup via `LoggingApplicationListener`.

The initialization process specifically calls `LogbackLoggingSystem.beforeInitialize()`, which installs an `SLF4JBridgeHandler` to route `java.util.logging` (JUL) messages through SLF4J. According to the source code, the system invokes `configureJdkLoggingBridgeHandler()` to establish this bridge before Logback fully initializes, ensuring uniform log formatting across all logging frameworks.

## Common Pitfalls When Setting Up SLF4J with Logback

Despite automatic configuration, several classpath and configuration ordering issues can cause runtime errors or silent logging failures.

### Multiple SLF4J Bindings on the Classpath

SLF4J requires exactly one binding implementation at runtime. If your classpath contains both `logback-classic` (the Logback binding) and another implementation like `slf4j-simple` or `slf4j-log4j12`, SLF4J emits a "Class path contains multiple SLF4J bindings" warning and arbitrarily selects the first one discovered. This often results in missing log output or incorrect formatting because Spring Boot's `LogbackLoggingSystem` initializes Logback while SLF4J routes calls to a different implementation.

Spring Boot prevents this at build time through [`CheckClasspathForProhibitedDependencies.java`](https://github.com/spring-projects/spring-boot/blob/main/CheckClasspathForProhibitedDependencies.java) in the `buildSrc` directory. This Gradle task explicitly forbids `jcl-over-slf4j` and other conflicting bindings from appearing in the production classpath.

### Using Incompatible SLF4J Implementations

Spring Boot expects `LogbackLoggingSystem` to serve as the primary logging implementation. If you explicitly include `slf4j-log4j12` or Log4j2 bindings without excluding Logback, Spring Boot will still attempt to initialize Logback while SLF4J routes calls to the other implementation. This creates duplicated or missing log entries and can trigger `NoSuchMethodError` exceptions if version mismatches exist between the manually added binding and the SLF4J API version managed by Spring Boot.

### Missing JUL-to-SLF4J Bridge Configuration

Applications using `java.util.logging` directly (common in third-party libraries) require the `SLF4JBridgeHandler` to capture those messages. Without this bridge, JUL messages print to the console in a separate format or disappear entirely. As implemented in `LogbackLoggingSystem.beforeInitialize()`, Spring Boot automatically installs this bridge when `jul-to-slf4j` is present on the classpath, but manual exclusions or custom logging systems can disable this behavior.

### Configuration File Precedence Issues

Spring Boot searches for Logback configuration files in a specific order defined in `LogbackLoggingSystem.getStandardConfigLocations()`: `logback-test.groovy`, [`logback-test.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-test.xml), `logback.groovy`, [`logback.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback.xml). If you provide a [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml) (which supports Spring property placeholders) but also have a plain [`logback.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback.xml) on the classpath, the plain file takes precedence and your Spring-specific configuration is ignored. This leads to unexpected log patterns or missing file appenders.

## Best Practices for SLF4J Logback Configuration

Following these practices ensures stable logging behavior across environments.

### Let Spring Boot Manage Dependencies

Include only `spring-boot-starter-logging` in your build configuration. This starter transitively provides `logback-classic`, `slf4j-api`, and the JUL bridge with versions tested for compatibility. Avoid manually declaring SLF4J or Logback versions to prevent `NoSuchMethodError` and linkage errors.

### Exclude Conflicting Bindings

When adding third-party libraries that transitively include `slf4j-simple`, `slf4j-log4j12`, or `log4j-over-slf4j`, explicitly exclude these modules. In Gradle, use the `exclude` directive; in Maven, use `<exclusions>`.

### Use logback-spring.xml for Custom Configuration

Place your custom Logback configuration in [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml) at the root of the classpath (typically `src/main/resources`). This file supports Spring Environment properties and is loaded before plain [`logback.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback.xml), ensuring your settings take precedence while still respecting Spring Boot's default initialization logic, including the automatic installation of the JUL-to-SLF4J bridge in `beforeInitialize()`.

### Verify the Logging System in Tests

Write integration tests that assert the correct `LoggingSystem` implementation is active. Verify that `LoggerFactory.getILoggerFactory()` returns a `ch.qos.logback.classic.LoggerContext` instance, confirming that Logback is the active binding and no conflicting implementations are present.

## Code Examples

*Gradle configuration excluding stray SLF4J bindings:*

```gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    // Automatically includes spring-boot-starter-logging
    
    // Exclude unwanted binding from a third-party library
    implementation('com.example:some-lib:1.2.3') {
        exclude group: 'org.slf4j', module: 'slf4j-simple'
    }
}

```

*Maven configuration with exclusions:*

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.example</groupId>
    <artifactId>some-lib</artifactId>
    <version>1.2.3</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </exclusion>
    </exclusions>
</dependency>

```

*Custom logback-spring.xml with Spring placeholders:*

```xml
<configuration>
    <property name="LOG_PATTERN" 
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <root level="${logging.level.root:INFO}">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

```

*JUnit test verifying Logback is the active binding:*

```java
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;

class LoggingSystemTest {

    @Test
    void logbackIsTheActiveSlf4jBinding() {
        // Verify that LoggerFactory returns Logback's LoggerContext
        assertThat(LoggerFactory.getILoggerFactory())
            .isInstanceOf(ch.qos.logback.classic.LoggerContext.class);
    }
}

```

## Summary

- Spring Boot automatically wires SLF4J with Logback through `spring-boot-starter-logging` and [`LogbackLoggingSystem.java`](https://github.com/spring-projects/spring-boot/blob/main/LogbackLoggingSystem.java), installing the JUL-to-SLF4J bridge during initialization via `beforeInitialize()`.
- Runtime errors typically stem from **multiple SLF4J bindings** on the classpath, incompatible implementations like `slf4j-log4j12`, or configuration file precedence conflicts between [`logback.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback.xml) and [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml).
- The build-time check in [`CheckClasspathForProhibitedDependencies.java`](https://github.com/spring-projects/spring-boot/blob/main/CheckClasspathForProhibitedDependencies.java) explicitly forbids `jcl-over-slf4j` to prevent recursive logging and class-loader leaks.
- Best practices include letting Spring Boot manage dependency versions, excluding transitive SLF4J bindings from third-party libraries, using [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml) for custom configurations, and verifying the active `LoggerContext` in integration tests.

## Frequently Asked Questions

### What happens if I have multiple SLF4J bindings on the classpath?

SLF4J will emit a warning message indicating that multiple bindings were found and will arbitrarily select the first one discovered on the classpath. This often results in missing log output or incorrect formatting because Spring Boot's `LogbackLoggingSystem` initializes Logback while SLF4J routes calls to a different implementation. To prevent this, exclude all transitive bindings except `logback-classic` using Gradle or Maven exclusions.

### Can I use Log4j2 instead of Logback with Spring Boot?

Yes, but you must explicitly exclude `spring-boot-starter-logging` and include `spring-boot-starter-log4j2`. Spring Boot will then use `Log4J2LoggingSystem` instead of `LogbackLoggingSystem`. However, mixing Log4j2 bindings with Logback on the classpath causes the same multiple-binding runtime errors. Ensure you completely exclude `logback-classic` when switching to Log4j2 to maintain a single SLF4J binding.

### Why does Spring Boot prohibit jcl-over-slf4j?

Spring Boot's build-time check in [`CheckClasspathForProhibitedDependencies.java`](https://github.com/spring-projects/spring-boot/blob/main/CheckClasspathForProhibitedDependencies.java) rejects `jcl-over-slf4j` because it can cause recursive logging loops and class-loader leaks. This library bridges Apache Commons Logging (JCL) to SLF4J, but Spring Boot already handles JCL bridging internally through `spring-jcl`. Including `jcl-over-slf4j` creates a circular reference where logging calls bounce between frameworks, potentially causing `StackOverflowError` or memory leaks in application containers.

### How do I customize Logback configuration without losing Spring Boot defaults?

Place your custom Logback configuration in [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml) at the root of the classpath (typically `src/main/resources`). According to `LogbackLoggingSystem.getStandardConfigLocations()`, Spring Boot loads [`logback-spring.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback-spring.xml) after test configurations but before plain [`logback.xml`](https://github.com/spring-projects/spring-boot/blob/main/logback.xml), and it supports Spring Environment placeholders like `${logging.file.name}`. This approach ensures your custom appenders and patterns apply while preserving Spring Boot's automatic initialization logic, including the JUL-to-SLF4J bridge installed in `beforeInitialize()`.