eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

Log4j 2 uses plugins like Appenders and Layouts to format and output logs. These are known as core plugins, and Log4j 2 provides a lot of options for us to choose from.

However, in some cases, we may also need to extend the existing plugin or even write custom ones.

In this tutorial, we’ll use the Log4j 2 extension mechanism to implement custom plugins.

2. Extending Log4j 2 Plugins

Plugins in Log4j 2 are broadly divided into five categories:

  1. Core Plugins
  2. Convertors
  3. Key Providers
  4. Lookups
  5. Type Converters

Log4j 2 allows us to implement custom plugins in all the above categories using a common mechanism. Moreover, it also allows us to extend existing plugins with the same approach.

In Log4j 1.x, the only way to extend an existing plugin is to override its implementation class. On the other hand, Log4j 2 makes it easier to extend existing plugins by annotating a class with @Plugin.

In the following sections, we’ll implement a custom plugin in a few of these categories.

3. Core Plugin

3.1. Implementing a Custom Core Plugin

Key elements like Appenders, Layouts, and Filters are known as core plugins in Log4j 2. Although there is a diverse list of such plugins, in some cases, we may need to implement a custom core plugin. For example, consider a ListAppender that only writes log records into an in-memory List:

@Plugin(name = "ListAppender", 
  category = Core.CATEGORY_NAME, 
  elementType = Appender.ELEMENT_TYPE)
public class ListAppender extends AbstractAppender {

    private List<LogEvent> logList;

    protected ListAppender(String name, Filter filter) {
        super(name, filter, null);
        logList = Collections.synchronizedList(new ArrayList<>());
    }

    @PluginFactory
    public static ListAppender createAppender(
      @PluginAttribute("name") String name, @PluginElement("Filter") final Filter filter) {
        return new ListAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        if (event.getLevel().isLessSpecificThan(Level.WARN)) {
            error("Unable to log less than WARN level.");
            return;
        }
        logList.add(event);
    }
}

We have annotated the class with @Plugin that allows us to name our plugin. Also, the parameters are annotated with @PluginAttribute. The nested elements like filter or layout are passed as @PluginElement. Now we can refer this plugin in the configuration using the same name:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude"
    packages="com.baeldung" status="WARN">
    <Appenders>
        <ListAppender name="ListAppender">
            <BurstFilter level="INFO" rate="16" maxBurst="100"/>
        </ListAppender>
    </Appenders>
    <Loggers
        <Root level="DEBUG">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="ListAppender" />
        </Root>
    </Loggers>
</Configuration>

3.2. Plugin Builders

The example in the last section is rather simple and only accepts a single parameter name. Generally speaking, core plugins like appenders are much more complex and usually accepts several configurable parameters.

For example, consider an appender that writes logs into Kafka:

<Kafka2 name="KafkaLogger" ip ="127.0.0.1" port="9010" topic="log" partition="p-1">
    <PatternLayout pattern="%pid%style{%message}{red}%n" />
</Kafka2>

To implement such appenders, Log4j 2 provides a plugin builder implementation based on the Builder pattern:

@Plugin(name = "Kafka2", category = Core.CATEGORY_NAME)
public class KafkaAppender extends AbstractAppender {

    public static class Builder implements org.apache.logging.log4j.core.util.Builder<KafkaAppender> {

        @PluginBuilderAttribute("name")
        @Required
        private String name;

        @PluginBuilderAttribute("ip")
        private String ipAddress;

        // ... additional properties

        // ... getters and setters

        @Override
        public KafkaAppender build() {
            return new KafkaAppender(
              getName(), getFilter(), getLayout(), true, new KafkaBroker(ipAddress, port, topic, partition));
        }
    }

    private KafkaBroker broker;

    private KafkaAppender(String name, Filter filter, Layout<? extends Serializable> layout, 
      boolean ignoreExceptions, KafkaBroker broker) {
        super(name, filter, layout, ignoreExceptions);
        this.broker = broker;
    }

    @Override
    public void append(LogEvent event) {
        connectAndSendToKafka(broker, event);
    }
}

In short, we introduced a Builder class and annotated the parameters with @PluginBuilderAttribute. Because of this, KafkaAppender accepts the Kafka connection parameters from the config shown above.

3.3. Extending an Existing Plugin

We can also extend an existing core plugin in Log4j 2. We can achieve this by giving our plugin the same name as an existing plugin. For example, if we’re extending the RollingFileAppender:

@Plugin(name = "RollingFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class RollingFileAppender extends AbstractAppender {

    public RollingFileAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout);
    }
    @Override
    public void append(LogEvent event) {
    }
}

Notably, we now have two appenders with the same name. In such a scenario, Log4j 2 will use the appender that is discovered first. We’ll see more on plugin discovery in a later section.

Please note that Log4j 2 discourages multiple plugins with the same name. It’s better to implement a custom plugin instead and use that in the logging configuration.

4. Converter Plugin

The layout is a powerful plugin in Log4j 2It allows us to define the output structure for our logs. For instance, we can use JsonLayout for writing the logs in JSON format.

Another such plugin is the PatternLayoutIn some cases, an application wants to publish information like thread id, thread name, or timestamp with each log statement. PatternLayout plugin allows us to embed such details through a conversion pattern string in the configuration:

<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>

Here, %d is the conversion pattern. Log4j 2 converts this %d pattern through a DatePatternConverter that understands the conversion pattern and replaces it with the formatted date or timestamp.

Now suppose an application running inside a Docker container wants to print the container name with every log statement. To do this, we’ll implement a DockerPatterConverter and change the above config to include the conversion string:

@Plugin(name = "DockerPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({"docker", "container"})
public class DockerPatternConverter extends LogEventPatternConverter {

    private DockerPatternConverter(String[] options) {
        super("Docker", "docker");
    }

    public static DockerPatternConverter newInstance(String[] options) {
        return new DockerPatternConverter(options);
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        toAppendTo.append(dockerContainer());
    }

    private String dockerContainer() {
        return "container-1";
    }
}

So we implemented a custom DockerPatternConverter similar to the date pattern. It will replace the conversion pattern with the name of the Docker container.

This plugin is similar to the core plugin we implemented earlier. Notably, there is just one annotation that is different from the last plugin. @ConverterKeys annotation accepts the conversion pattern for this plugin.

As a result, this plugin will convert %docker or %container pattern string into the container name in which the application is running:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude" packages="com.baeldung" status="WARN">
    <Appenders>
        <xi:include href="log4j2-includes/console-appender_pattern-layout_colored.xml" />
        <Console name="DockerConsoleLogger" target="SYSTEM_OUT">
            <PatternLayout pattern="%pid %docker %container" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.baeldung.logging.log4j2.plugins" level="INFO">
            <AppenderRef ref="DockerConsoleLogger" />
        </Logger>
    </Loggers>
</Configuration>

5. Lookup Plugin

Lookup plugins are used to add dynamic values in the Log4j 2 configuration file. They allow applications to embed runtime values to some properties in the configuration file. The value is added through a key-based lookup in various sources like a file system, database, etc.

One such plugin is the DateLookupPlugin that allows replacing a date pattern with the current system date of the application:

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${date:MM-dd-yyyy}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

In this sample configuration file, RollingFileAppender uses a date lookup where the output will be in MM-dd-yyyy format. As a result, Log4j 2 writes logs to an output file with a date suffix.

Similar to other plugins, Log4j 2 provides a lot of sources for lookups. Moreover, it makes it easy to implement custom lookups if a new source is required:

@Plugin(name = "kafka", category = StrLookup.CATEGORY)
public class KafkaLookup implements StrLookup {

    @Override
    public String lookup(String key) {
        return getFromKafka(key);
    }

    @Override
    public String lookup(LogEvent event, String key) {
        return getFromKafka(key);
    }

    private String getFromKafka(String topicName) {
        return "topic1-p1";
    }
}

So KafkaLookup will resolve the value by querying a Kafka topic. We’ll now pass the topic name from the configuration:

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${kafka:topic-1}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

We replaced the date lookup in our earlier example with Kafka lookup that will query topic-1.

Since Log4j 2 only calls the default constructor of a lookup plugin, we didn’t implement the @PluginFactory as we did in earlier plugins.

6. Plugin Discovery

Finally, let’s understand how Log4j 2 discovers the plugins in an application. As we saw in the examples above, we gave each plugin a unique name. This name acts as a key, which Log4j 2 resolves to a plugin class.

There’s a specific order in which Log4j 2 performs a lookup to resolve a plugin class:

  1. Serialized plugin listing file in the log4j2-core library. Specifically, a Log4j2Plugins.dat is packaged inside this jar to list the default Log4j 2 plugins
  2. Similar Log4j2Plugins.dat file from the OSGi bundles
  3. A comma-separated package list in the log4j.plugin.packages system property
  4. In programmatic Log4j 2 configuration, we can call PluginManager.addPackages() method to add a list of package names
  5. A comma-separated list of packages can be added in the Log4j 2 configuration file

As a prerequisite, annotation processing must be enabled to allow Log4j 2 to resolve plugin by the name given in the @Plugin annotation.

Since Log4j 2 uses names to look up the plugin, the above order becomes important. For example, if we have two plugins with the same name, Log4j 2 will discover the plugin that is resolved first. Therefore, if we need to extend an existing plugin in Log4j 2we must package the plugin in a separate jar and place it before the log4j2-core.jar.

7. Conclusion

In this article, we looked at the broad categories of plugins in Log4j 2. We discussed that even though there is an exhaustive list of existing plugins, we may need to implement custom plugins for some use cases.

Later, we looked at the custom implementation of some useful plugins. Furthermore, we saw how Log4j 2 allows us to name these plugins and subsequently use this plugin name in the configuration file. Finally, we discussed how Log4j 2 resolves plugins based on this name.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)