Log4j2 Console Appender Example: Patterns, Colors, JSON

Configure the Log4j2 Console appender with PatternLayout in log4j2.xml, properties or YAML, with real output. Includes colored levels, JSON logs, ThresholdFilter and fixes for a missing config file.

Log4j2

The Log4j2 Console appender writes our log messages to the terminal, through System.out or System.err. We add a Console element to log4j2.xml and give it a PatternLayout, whose pattern decides how each line looks.

We use the Console appender while we develop an app on our machine, and in containers, where Docker and Kubernetes collect everything the app prints to the console.

The following example sends every INFO or higher message of a small recipe app to System.out, with the time, the level, the thread and the short logger name in each line.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %logger{1} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="console"/>
    </Root>
  </Loggers>
</Configuration>

The project needs two dependencies. The log4j-api artifact has the Logger interface that our code calls, and log4j-core contains the implementation, such as the appenders, the layouts and the code that reads the config file.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api</artifactId>
  <version>2.26.1</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.26.1</version>
</dependency>
Logger logger = LogManager.getLogger(ConsoleAppenderExample.class);

logger.debug("Loading recipe '{}'", "pancakes");                 // not printed: root level is info
logger.info("Recipe '{}' saved with {} steps", "pancakes", 4);
logger.warn("Recipe '{}' has no steps", "waffles");
logger.error("Could not load recipe '{}'", "soup",
    new IllegalStateException("File not found: soup.txt"));
11:57:01.834 INFO  [main] ConsoleAppenderExample - Recipe 'pancakes' saved with 4 steps
11:57:01.849 WARN  [main] ConsoleAppenderExample - Recipe 'waffles' has no steps
11:57:01.857 ERROR [main] ConsoleAppenderExample - Could not load recipe 'soup'
java.lang.IllegalStateException: File not found: soup.txt
	at com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.logRecipes(ConsoleAppenderExample.java:27)
	at com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.main(ConsoleAppenderExample.java:20)

Notice that the DEBUG line is missing because the root level is info, and that the stack trace of the exception follows the ERROR line.

Next, we look at the target setting and every common pattern token. Then we add colors, JSON output, the properties and YAML formats, filters, and fixes for a missing config file.

1. What Is the Log4j2 Console Appender?

Log4j2 splits logging into three parts, and each part has one job.

  • A logger is the object our code calls, for example logger.info(…). It checks the level and ignores messages below that level.
  • An appender decides where a message goes, such as the console, a file or a database, and the Console appender is one of them.
  • A layout decides how the message looks, and PatternLayout turns each message into one line of text.

In the intro config, the Root logger sends every INFO or higher message to the appender named console. The AppenderRef element connects the logger to the appender by the appender’s name.

The Console element has a few attributes, but only name is required, and target defaults to SYSTEM_OUT.

AttributeDefaultWhat it does
namenone (required)The name that AppenderRef points to
targetSYSTEM_OUTSYSTEM_OUT writes to System.out. SYSTEM_ERR writes to System.err
followfalseWith true, the appender notices a later System.setOut() call and writes to the new stream
directfalseWith true, the appender writes straight to the operating system output and skips System.out. Faster, but follow stops working
immediateFlushtrueWrites each message out right away instead of keeping it in a buffer
ignoreExceptionstrueWith false, an error inside the appender is thrown to our code

The Console appender has a few more attributes for rare cases, and the other elements of the file follow the Log4j2 XML configuration format.

2. Console Appender Example With SYSTEM_OUT and SYSTEM_ERR

A Java program has two output streams, System.out for standard output and System.err for error output, and both appear in the same terminal window.

Tools can still treat the two streams differently. For example, a shell can write each stream to a different file.

java -jar recipes.jar > app.log 2> errors.log

The target attribute selects the stream, and without target the appender writes to System.out.

<Console name="console" target="SYSTEM_ERR">
  <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{1} - %msg%n"/>
</Console>

Most applications keep the default SYSTEM_OUT, and because Docker and Kubernetes read both streams, System.out alone is fine there.

A common setup sends normal messages to System.out and warnings and errors to System.err, which needs two appenders and a filter, as we will see in section 6.

3. PatternLayout Tokens

A pattern is a template for each log line. Log4j2 prints plain text in the pattern as it is and replaces every token that starts with % with a value from the log event.

For example, %-5level prints the level and pads it to 5 characters, which keeps the columns aligned. A support engineer who searches the log for one request thread needs %t in every line.

The example values belong to the ERROR call of the intro example, so the method is logRecipes and the line number is 27.

TokenPrintsExample value
%dDate and time, default format2026-10-04 12:00:19,906
%d{ISO8601}Date and time in ISO-8601 format2026-10-04T12:00:19,906
%d{HH:mm:ss.SSS}Time only, with a custom format12:00:19.906
%p or %levelLog levelERROR
%-5levelLevel, padded to 5 characters[INFO ] (brackets added in the pattern)
%tThread namemain
%c or %loggerFull logger namecom.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample
%c{1}Last part of the logger nameConsoleAppenderExample
%c{1.}Each package part shortened to one letterc.h.d.l.c.ConsoleAppenderExample
%C{1}Class that made the call (slow)ConsoleAppenderExample
%MMethod that made the call (slow)logRecipes
%LLine number of the call (slow)27
%pidProcess ID of the JVM22612
%m or %msgThe message textCould not load recipe ‘soup’
%ex{short.message}Only the exception messageFile not found: soup.txt
%nLine break for the current OS(new line)

The tokens %C, %M and %L slow down logging. Log4j2 has to walk the call stack for every message to find these values, so we use them in development only.

In Log4j2, %c{36} means “the last 36 parts of the logger name”, not “36 characters”. Our logger name has only 6 parts, so %c{36} printed the full name. Logback treats the same number as a length, which gives different output for the same pattern.

When a message has an exception and the pattern has no exception token, Log4j2 adds the stack trace after the line, which is why the intro output shows the stack trace without any exception token. We can combine the tokens into many other conversion patterns.

4. Colored Console Output With %highlight

Colors make the ERROR lines easier to find in a terminal full of log lines, and Log4j2 has two color tokens for them.

  • %highlight{…} colors the text inside the braces by log level.
  • %style{…}{color} gives the text inside the braces one fixed color, for example cyan.
<Console name="console" target="SYSTEM_OUT">
  <PatternLayout disableAnsi="false"
      pattern="%d{HH:mm:ss.SSS} %highlight{%-5level} %style{%logger{1}}{cyan} - %msg%n"/>
</Console>

The terminal shows a different color for each level of the four messages.

Console output of the color pattern. DEBUG and the logger name are cyan, INFO is green, WARN is yellow and ERROR is bold red. The legend shows the ANSI code for each color
%highlight picks the color from the level. %style uses the same color for every line.

The colors come from ANSI escape codes (short control sequences such as ESC[32m for green or ESC[m for reset), which the terminal reads to change the color.

A file or a log tool does not interpret the codes, so in a file they appear as extra characters.

11:57:13.990 ^[[32mINFO ^[[m ^[[36mConsoleAppenderExample^[[m - Recipe 'pancakes' saved with 4 steps

PatternLayout has two switches that keep the codes out of files and pipes.

  • The disableAnsi=”true” setting never writes color codes.
  • The noConsoleNoAnsi=”true” setting writes the codes only when the output goes to a terminal. With Java 25, both switches removed the codes when we sent the output into a pipe.

On Windows, the colors need Windows 10 or later. Log4j 2.25.0 removed the old JAnsi library support, because Windows 10 supports ANSI codes without an extra library, so Windows Terminal and the IntelliJ IDEA console show the colors.

The old cmd.exe window may print the raw codes, and for that window the Log4j2 manual suggests the registry value HKEY_CURRENT_USER\Console\VirtualTerminalLevel (DWORD 1). Otherwise, set disableAnsi=”true” on Windows.

The default colors are bold red for FATAL and ERROR, yellow for WARN, green for INFO, cyan for DEBUG and black for TRACE. We can change the color of each level inside the %highlight token.

5. JSON Console Logs With JsonTemplateLayout

In Docker and Kubernetes, a log tool such as Elasticsearch often reads the console output, and it reads JSON more reliably than free text because each value has its own field.

JsonTemplateLayout prints one JSON object per line and needs one more dependency.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-layout-template-json</artifactId>
  <version>2.26.1</version>
</dependency>

We replace PatternLayout with JsonTemplateLayout, and the rest of the config stays the same.

<Console name="console" target="SYSTEM_OUT">
  <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
</Console>
{"@timestamp":"2026-10-04T06:27:04.634Z","ecs.version":"1.2.0","log.level":"INFO","message":"Recipe 'pancakes' saved with 4 steps","process.thread.name":"main","log.logger":"com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample"}
{"@timestamp":"2026-10-04T06:27:04.646Z","ecs.version":"1.2.0","log.level":"WARN","message":"Recipe 'waffles' has no steps","process.thread.name":"main","log.logger":"com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample"}
{"@timestamp":"2026-10-04T06:27:04.646Z","ecs.version":"1.2.0","log.level":"ERROR","message":"Could not load recipe 'soup'","process.thread.name":"main","log.logger":"com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample","error.type":"java.lang.IllegalStateException","error.message":"File not found: soup.txt","error.stack_trace":"java.lang.IllegalStateException: File not found: soup.txt\n\tat com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.logRecipes(ConsoleAppenderExample.java:27)\n\tat com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.main(ConsoleAppenderExample.java:20)\n"}

EcsLayout.json uses the Elastic Common Schema (ECS), the field names that Elasticsearch uses, and ECS is also the default template. Other built-in templates are LogstashJsonEventLayoutV1.json, GelfLayout.json and GcpLayout.json.

In the JSON output, the full stack trace is stored in one field, so a multi-line error is still one log entry.

The older JsonLayout is deprecated, and the JSON Template Layout manual recommends JsonTemplateLayout for new projects. We should not confuse JsonTemplateLayout with a Log4j2 configuration file written in JSON, which changes only the format of the config file.

6. Filtering Console Output With ThresholdFilter

A filter decides whether an appender prints a message, and ThresholdFilter does this by comparing the message level with a fixed level.

When the message level is the same or higher, the filter returns the onMatch value, and when it is lower, the filter returns the onMismatch value. Each value is ACCEPT, DENY or NEUTRAL.

For example, an operations team sends System.err to an alert tool and wants only warnings and errors there. The next config uses two Console appenders that both receive every message, and each filter accepts only half of the levels.

<Appenders>
  <!-- DEBUG and INFO go to System.out -->
  <Console name="stdout" target="SYSTEM_OUT">
    <ThresholdFilter level="warn" onMatch="DENY" onMismatch="ACCEPT"/>
    <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{1} - %msg%n"/>
  </Console>
  <!-- WARN, ERROR and FATAL go to System.err -->
  <Console name="stderr" target="SYSTEM_ERR">
    <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
    <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{1} - %msg%n"/>
  </Console>
</Appenders>
<Loggers>
  <Root level="debug">
    <AppenderRef ref="stdout"/>
    <AppenderRef ref="stderr"/>
  </Root>
</Loggers>
Flow diagram. Four logger calls pass the root logger at level debug. Each event goes to both Console appenders. The stdout appender denies WARN and above and prints DEBUG and INFO to System.out. The stderr appender accepts WARN and above and prints WARN and ERROR to System.err
The root logger passes all four events. The two filters split the events between System.out and System.err.

We run the program twice and hide one stream each time. The shell option 2>/dev/null hides System.err, whereas 2>&1 >/dev/null shows only System.err.

11:57:07.404 DEBUG ConsoleAppenderExample - Loading recipe 'pancakes'
11:57:07.406 INFO  ConsoleAppenderExample - Recipe 'pancakes' saved with 4 steps
11:57:11.296 WARN  ConsoleAppenderExample - Recipe 'waffles' has no steps
11:57:11.299 ERROR ConsoleAppenderExample - Could not load recipe 'soup'
java.lang.IllegalStateException: File not found: soup.txt
	at com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.logRecipes(ConsoleAppenderExample.java:27)
	at com.howtodoinjava.demo.log4j2.console.ConsoleAppenderExample.main(ConsoleAppenderExample.java:20)

A filter inside Console affects only that appender. Log4j2 checks the logger level before the filter, so with Root level=”info” the DEBUG line never reaches either appender.

To print a range of levels, such as INFO to WARN only, we use a LevelRangeFilter instead. Both filters compare levels in the order of the Log4j2 log levels, from FATAL to TRACE.

7. Console Appender in log4j2.properties and log4j2.yaml

Log4j2 reads the same configuration from four file formats, namely XML, properties, YAML and JSON. Each format needs the right file name, such as log4j2.properties, in src/main/resources, and we keep only one of these files in a project.

The properties format writes each XML attribute as a key. The word after appender. (here console) is a label that we choose, and it groups the keys of one appender.

appender.console.type = Console
appender.console.name = console
appender.console.target = SYSTEM_OUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{HH:mm:ss.SSS} %-5level [%t] %logger{1} - %msg%n

rootLogger.level = info
rootLogger.appenderRef.console.ref = console

Keys such as log4j.appender.console=org.apache.log4j.ConsoleAppender belong to Log4j 1.x, and Log4j2 ignores them. Use the appender.<label>.type form of the Log4j2 properties file instead.

The YAML file uses the same structure as the XML file. Log4j2 reads YAML with the Jackson library, so YAML needs the jackson-dataformat-yaml dependency (version 2.22.3).

Configuration:
  Appenders:
    Console:
      name: console
      target: SYSTEM_OUT
      PatternLayout:
        pattern: "%d{HH:mm:ss.SSS} %-5level [%t] %logger{1} - %msg%n"
  Loggers:
    Root:
      level: info
      AppenderRef:
        ref: console

The properties and YAML files print the same lines as the XML file of the intro example. Any other Log4j2 element goes into the Log4j2 YAML file with the same nesting as in XML.

8. Log4j2 Console Appender FAQs

Most console problems come from a missing config file or a missing dependency, such as the wrong SLF4J bridge.

8.1. What does Log4j2 print when log4j2.xml is missing?

Without a config file, Log4j2 falls back to a built-in default configuration. The default configuration prints only ERROR messages, to the console. Our INFO and WARN messages never appear, and Log4j2 shows no warning about the missing file.

2026-10-04T06:25:02.167204265Z main ERROR Could not load recipe 'soup'

Log4j2 has its own small internal logger, the status logger, which reports problems with the configuration. By default, it prints only errors, so we raise its level to see the warning.

With the JVM option -Dlog4j2.statusLoggerLevel=WARN, the status logger prints the missing-file warning.

2026-10-04T06:25:06.261550370Z main WARN No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2

A wrong file name in log4j2.configurationFile produces an error instead (the hex value differs on every run).

2026-10-04T06:24:43.795758827Z main ERROR Reconfiguration failed: No configuration found for '7c84929b' at 'null' in 'null'

For example, a new teammate copies log4j2.xml into the project root instead of src/main/resources, and the app prints only errors. In both cases, put log4j2.xml in src/main/resources, or point log4j2.configurationFile to the right path. The JVM option -Dlog4j2.debug=true prints every step of the config search.

The status attribute on Configuration (for example status=”WARN”) is deprecated since Log4j 2.24.0. It takes effect only after the file is found, so it does not report problems that happen before that point. The status logger manual recommends the log4j2.statusLoggerLevel system property.

8.2. Why does Log4j2 print “Log4j API could not find a logging provider”?

The project has log4j-api but not log4j-core, so Log4j2 has no appenders and ignores log4j2.xml. Log4j2 prints an error, and only the ERROR message of our code reaches the console.

2026-10-04T06:24:48.968386929Z main ERROR Log4j API could not find a logging provider.
ERROR NoConfig Could not load recipe 'soup'

Adding the log4j-core dependency in Maven or Gradle fixes the problem.

8.3. How do I load a config file with a different name?

Log4j2 looks for log4j2-test.xml first and then log4j2.xml (and the other formats) on the classpath. For any other name, we set the log4j2.configurationFile system property before the first logger is created.

java -Dlog4j2.configurationFile=log4j2-color.xml -jar recipes.jar

In a test or a small program, Configurator loads a file from code.

LoggerContext context = Configurator.initialize("recipes", "log4j2-console/log4j2-color.xml");
Logger logger = context.getLogger(ConsoleAppenderExample.class);

For tests, we put a log4j2-test.xml file for JUnit in src/test/resources.

8.4. How do I reuse one pattern in several appenders?

Log4j2 configs often declare the pattern once in a Properties block, for example as a property named LOG_PATTERN, and each appender then refers to the property with a lookup.

A lookup is a dollar sign followed by the property name in curly braces, and Log4j2 replaces it with the value when the config loads.

The property lookup syntax also supports default values. For two appenders, copying the pattern is also fine, as in section 6.

8.5. How do I send SLF4J logs to the Log4j2 Console appender?

Many libraries and Spring Boot use SLF4J, a common logging API. The log4j-slf4j2-impl bridge, a small adapter library, passes SLF4J calls to Log4j2, so the same Console appender prints the messages.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j2-impl</artifactId>
  <version>2.26.1</version>
</dependency>
Logger logger = LoggerFactory.getLogger(Slf4jConsoleExample.class);   // org.slf4j.Logger
logger.info("Recipe '{}' saved with {} steps", "pancakes", 4);
// 11:54:28.964 INFO  [main] Slf4jConsoleExample - Recipe 'pancakes' saved with 4 steps

Use log4j-slf4j2-impl with SLF4J 2.x, and log4j-slf4j-impl only with SLF4J 1.x.

8.6. Does the Console appender work in Spring Boot?

Yes. Spring Boot uses Logback by default, so to use Log4j2 we exclude spring-boot-starter-logging and add spring-boot-starter-log4j2.

Spring Boot then reads the Log4j2 configuration from log4j2.xml or log4j2-spring.xml in src/main/resources.

9. Conclusion

In log4j2.xml, the Console appender needs a Console element, a layout and an AppenderRef from a logger, and its target attribute selects System.out (the default) or System.err.

PatternLayout tokens set the format of each line, and %highlight adds colors in a terminal. For log tools, JsonTemplateLayout prints one JSON object per line.

ThresholdFilter splits levels between appenders, and when the console prints nothing, the status logger options from section 8 show the reason.

10. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. 0

    While configuring log4j2.properties I found 2 different sets of properties. Just wanted to know what is the different and which is correct.

    1.

    
    log4j.rootCategory=info,console
     
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.target=System.out
    log4j.appender.console.immediateFlush=true
    log4j.appender.console.encoding=UTF-8
    #log4j.appender.console.threshold=info
       
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.conversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSSZ} %p%m%n
    

    2.

    
    appender.console.type = Console
    appender.console.name = consoleAppender
    appender.console.layout.type = PatternLayout
    appender.console.layout.pattern = %d{yyyy-MM-dd'T'HH:mm:ss.SSSZ} %p%m%n
    

    While running the code 1 is working and the 2nd option is not working.

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.