Let's get started with a Microservice Architecture with Spring Cloud:
Introduction to Java Logging
Last updated: February 11, 2026
1. Overview
Logging is a powerful aid for understanding and debugging a program’s run-time behavior. Logs capture and persist important data, making it available for analysis at any time.
In this tutorial, we discuss the most popular Java logging frameworks, Log4j 2 and Logback, along with their predecessor Log4j. In addition, we briefly touch on SLF4J, a logging facade that provides a common interface for different logging frameworks.
2. Enabling Logging
The logging frameworks discussed in our article share the notion of loggers, appenders, and layouts. Enabling logging inside the project follows three common steps:
- Adding needed libraries
- Configuration
- Placing log statements
The upcoming sections discuss the steps for each framework individually.
3. Log4j 2
Log4j 2 is a new and improved version of the Log4j logging framework. The most compelling improvement is the possibility of asynchronous logging.
Log4j 2 requires the following libraries:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.25.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.3</version>
</dependency>
The latest versions of log4j-api and log4j-core are available in the official documentation.
3.1. Configuration
Configuring Log4j 2 is based on the main configuration log4j2.xml file.
The first thing to configure is the appender, which determines where the log message is routed, for instance, to the console, a file, a socket, etc.
Log4j 2 provides many appenders for different use cases, which are documented on the official Log4j 2 site.
Let’s take a look at a simple config example:
<Configuration status="debug" name="baeldung" packages="">
<Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
</Console>
</Appenders>
</Configuration>
We can set a name for each appender; for example, use console instead of stdout.
The PatternLayout element determines how the message should look. In our example, the pattern is set based on the pattern param, where %d determines the date pattern, %p the log level output, %m the output of the logged message, and %n adds a new line symbol. More info about patterns can be found on the official Log4j 2 page. Finally, to enable an appender (or multiple), we need to add it to the <Loggers> section:
<Loggers>
<Root level="error">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
With this configuration in place, Log4j 2 can route log messages to the configured appenders according to the defined logging levels.
3.2. Logging to File
Sometimes, we may need to add logging to a file. For this, we can add fout logger to our configuration:
<Appenders>
<File name="fout" fileName="baeldung.log" append="true">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</PatternLayout>
</File>
</Appenders>
The File appender has several parameters that can be configured:
- file – determines the file name of the log file
- append – the default value for this param is true, meaning that by default, a File appender will append to an existing file and not truncate it
- PatternLayout – defines the format of each log entry, as described in the previous section
In order to enable File Appender, we need to add it to the <Root> section:
<Root level="INFO">
<AppenderRef ref="stdout" />
<AppenderRef ref="fout"/>
</Root>
With this configuration in place, Log4j 2 can write log messages to both the console and the specified log file.
3.3. Asynchronous Logging
Log4j 2 supports asynchronous logging, which can make our application faster by letting log messages be processed in the background. There are two ways to enable it, and they work differently. Both methods need the LMAX Disruptor library, which we need to add to our pom.xml:
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>4.0.0</version>
</dependency>
Using <AsyncRoot>:
<AsyncRoot level="DEBUG">
<AppenderRef ref="stdout" />
<AppenderRef ref="fout"/>
</AsyncRoot>
Here’s the breakdown:
- Only the loggers inside <AsyncRoot> become asynchronous
- All other loggers stay synchronous
- Useful if we want some loggers to be async and others to stay normal
Works the same way as the <Root> configuration but only affects loggers defined within <AsyncRoot>.
Using the system property:
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
Log4j 2 provides two asynchronous context selectors. The AsyncLoggerContextSelector configures all loggers to operate asynchronously, while BasicAsyncLoggerContextSelector is intended for scenarios that require more fine-grained control over asynchronous behavior.
Using the AsyncLoggerContextSelector system property results in the following behavior:
- Makes all loggers in our application asynchronous
- Convenient for high-performance logging
- It should not be used together with <AsyncRoot> or <AsyncLogger>, since it can create extra background threads
- We can verify that asynchronous logging is enabled by checking the org.apache.logging.log4j2 -> AsyncDefault MBean in JConsole, which exposes details such as buffer size and event processing metrics
When using the system property, all loggers become asynchronous as long as the configuration uses the standard <Root> and <Logger> elements. In contrast, <AsyncRoot> and <AsyncLogger> are meant for cases where asynchronous and synchronous loggers need to be mixed. These two approaches should not be combined, as doing so creates two separate background logging threads.
Both methods use the LMAX Disruptor library, just as appenders and layouts to control where and how log messages are written.
We can, of course, read more about the configuration of the Log4j2 async logger and see some performance diagrams on the Log4j2 official page.
3.4. Usage
The following is a simple example that demonstrates the use of Log4j for logging:
public class Log4jExample {
private static Logger logger = LogManager.getLogger(Log4jExample.class);
public static void main(String[] args) {
logger.debug("Debug log message");
logger.info("Info log message");
logger.error("Error log message");
}
}
After running, the application logs the following messages to both the console and file named baeldung.log:
2016-06-16 17:02:13 INFO Info log message
2016-06-16 17:02:13 ERROR Error log message
Now, let’s explore what happens if we elevate the root log level to ERROR:
<level value="ERROR" />
Here’s what the output looks like:
2016-06-16 17:02:13 ERROR Error log message
As we can see, changing the log level to the upper parameter causes the messages with lower log levels not to be printed to appenders.
Method logger.error can also be used to log an exception that occurred:
try {
// Here some exception can be thrown
} catch (Exception e) {
logger.error("Error log message", throwable);
}
Our example above illustrates how log levels control which messages are emitted and how Log4j 2 can be used to record both simple messages and exceptions.
3.5. Package Level Configuration
Here, let’s assume that we need to show messages with the log level TRACE, for example, from a specific package such as com.baeldung.log4j2:
logger.trace("Trace log message");
For all other packages, we want to continue logging only INFO messages. Meanwhile, let’s keep in mind that TRACE is lower than the root log level INFO that we specified in the configuration.
To enable logging only for one of the packages, we need to add the following section before <Root> to our log4j2.xml:
<Logger name="com.baeldung.log4j2" level="debug">
<AppenderRef ref="stdout"/>
</Logger>
This section enables logging for the com.baeldung.log4j package and our output will look like this:
2016-06-16 17:02:13 TRACE Trace log message
2016-06-16 17:02:13 DEBUG Debug log message
2016-06-16 17:02:13 INFO Info log message
2016-06-16 17:02:13 ERROR Error log message
This approach makes it possible to increase logging detail for a specific package while keeping the rest of the application’s logging unchanged.
4. Logback
Logback is meant to be an improved version of Log4j, developed by the same developer who made Log4j. It also has many more features than Log4j, with many of them being introduced into Log4j 2 as well. For additional information, we can take a quick look at all of the advantages of Logback on the official site.
Let’s start by adding the following dependency to the pom.xml:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.27</version>
</dependency>
Let’s note that Logback 1.5.x requires JDK 11+.
This dependency transitively pulls in another two dependencies, the logback-core and slf4j-api. Notably, the latest version of Logback can be found on Maven Central.
4.1. Configuration
Let’s now have a look at an example of Logback configuration:
<configuration>
# Console appender
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
# Pattern of log message for console appender
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</layout>
</appender>
# File appender
<appender name="fout" class="ch.qos.logback.core.FileAppender">
<file>baeldung.log</file>
<append>false</append>
<encoder>
# Pattern of log message for file appender
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</pattern>
</encoder>
</appender>
# Override log level for specified package
<logger name="com.baeldung.log4j" level="TRACE"/>
<root level="INFO">
<appender-ref ref="stdout" />
<appender-ref ref="fout" />
</root>
</configuration>
Logback uses SLF4J as an interface, so we need to import SLF4J’s Logger and LoggerFactory.
4.2. SLF4J
SLF4J provides a common interface and abstraction for most of the Java logging frameworks. It acts as a facade and provides a standardized API for accessing the underlying features of the logging framework.
Logback uses SLF4J as a native API for its functionality:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4jExample {
private static Logger logger = LoggerFactory.getLogger(Log4jExample.class);
public static void main(String[] args) {
logger.debug("Debug log message");
logger.info("Info log message");
logger.error("Error log message");
}
}
The output remains the same as in previous examples.
5. Log4J
Finally, let’s have a look at the venerable Log4j logging framework.
Before we proceed, let’s keep in mind that Log4j 1.x has reached end of life and is no longer actively maintained. As a result, it does not receive security updates and is not recommended for new applications. For existing projects, migrating to Log4j 2 or Logback is strongly encouraged.
At this point, it’s outdated, but worth discussing as it lays the foundation for its more modern successors.
Many of the configuration details match those discussed in the Log4j 2 section.
5.1. Configuration
First, we need to add the Log4j library to our project’s pom.xml:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
The latest version of Log4j is available on Maven Central.
Let’s take a look at an example of a simple Log4j configuration with only one console appender:
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
<log4j:configuration debug="false">
<!--Console appender-->
<appender name="stdout" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss} %p %m%n" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="stdout" />
</root>
</log4j:configuration>
<log4j:configuration debug=”false”>, the open tag of the whole configuration, has the property debug. This property determines whether we want to add Log4j debug information to logs.
5.2. Usage
After we have added the Log4j library and configuration, we can use the logger in our code.
Let’s take a look at a simple example:
public class Log4jExample {
private static Logger logger = Logger.getLogger(Log4jExample.class);
public static void main(String[] args) {
logger.debug("Debug log message");
logger.info("Info log message");
logger.error("Error log message");
}
}
This example demonstrates the basic usage of Log4j 1.x to write log messages at different levels.
6. Conclusion
In this article, we present simple examples of how to use different logging frameworks, such as Log4j, Log4j2, and Logback. It covers simple configuration examples for all of the mentioned frameworks.
The examples that accompany the article can be found over on GitHub.
















