logback vs log4j2: Up to Double the Performance—Why It Matters

A benchmark shows log4j2 can be roughly twice as fast as logback, especially when thread count approaches twice the CPU cores, while logging method names and line numbers significantly degrade throughput; the article explains the underlying reasons, testing setup, and provides practical configuration and usage guidelines for both frameworks in Java and Spring projects.

Java Architect Handbook
Java Architect Handbook
Java Architect Handbook
logback vs log4j2: Up to Double the Performance—Why It Matters

Introduction

logback and log4j2 are popular Java logging frameworks. Performance differences can be significant in large‑scale production environments.

Relationship with SLF4J

SLF4J is a logging façade; logback and log4j2 are concrete implementations of its APIs.

Obtaining a Logger

Two common ways:

Method 1: Using Lombok (recommended)

@Slf4j
public class Main {}

Method 2: Direct SLF4J usage

private static final Logger LOG = LoggerFactory.getLogger(Main.class);

Performance Test Comparison

Test Environment

CPU: AMD Ryzen 5 3600 6‑Core (3.95 GHz)

Memory: 32 GB 2666 MHz

JDK: semeru‑11.0.20

JVM options: -Xms1000m -Xmx1000m

log4j2 version: 2.22.1

logback version: 1.4.14

Thread counts: 1, 8, 32, 128

Test method: warm‑up, then three runs, averaging the formal run

Log pattern length: ~129 characters

Results

log4j2 outperforms logback; its throughput is roughly twice that of logback.

Throughput peaks when the number of threads is about twice the CPU core count; it does not increase linearly with thread count.

Tip: Enabling method name and line number in the log pattern dramatically reduces performance. Removing the line number alone can make log4j2 more than twice as fast in single‑threaded tests.

Performance chart:

Additional chart:

Usage in Projects

logback in Spring Boot

Add the dependency and place logback.xml under src/main/resources:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>

Sample logback.xml configuration:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <file>log/output.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>log/output.log.%i</fileNamePattern>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>1MB</MaxFileSize>
        </triggeringPolicy>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

log4j2 in Spring (non‑Spring‑Boot) Projects

Exclude Spring Boot’s default logback and add log4j2 dependencies:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>${log4j.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>${log4j.version}</version>
</dependency>

Place log4j2.xml under src/main/resources:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property>
        <Property name="file.err.filename">log/err.log</Property>
        <Property name="file.err.pattern">log/err.%i.log.gz</Property>
    </Properties>
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="${log.pattern}"/>
        </Console>
        <RollingFile name="err" bufferedIO="true" fileName="${file.err.filename}" filePattern="${file.err.pattern}">
            <PatternLayout pattern="${log.pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="1 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="console" level="info"/>
            <AppenderRef ref="err" level="error"/>
        </Root>
    </Loggers>
</Configuration>

Best Practices

Configure log rotation (maximum file count and size) to prevent disks from filling up.

Standardize log format (e.g., method name first, then parameters) and implement meaningful toString() for objects.

Avoid expensive pattern elements such as method name and line number unless required, as they add noticeable overhead.

Test Code Example

package com.winjeg.demo;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

@Slf4j
public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);
    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
            128, 256, 1L, TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(512),
            new BasicThreadFactory.Builder().namingPattern("thread-%d").daemon(true).build());

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        execute(8, 160_000);
        long first = System.currentTimeMillis();
        execute(8, 160_000);
        System.out.printf("time cost, preheat:%d\t, formal:%d
", first - start, System.currentTimeMillis() - first);
    }

    private static void execute(int threadNum, int times) {
        List<Future<?>> futures = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            Future<?> f = EXECUTOR.submit(() -> {
                for (long j = 0; j < times; j++) {
                    LOG.info("main - info level ...this is a demo script, pure string log will be used!");
                }
            });
            futures.add(f);
        }
        futures.forEach(f -> {
            try { f.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); }
        });
    }
}

References

logback official benchmark results

log4j2 official benchmark results

Java logging comparison article (log4j vs logback vs log4j2)

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Performance BenchmarkSpring BootlogbackSLF4Jlog4j2Java logging
Java Architect Handbook
Written by

Java Architect Handbook

Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.