log4j
logging
java
thread-id
programming-tips

Printing thread id in log file using log4j

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When you are debugging a multi-threaded Java application, knowing which thread produced a particular log line can save you hours of confusion. Apache Log4j (and its successor Log4j2) let you embed thread information directly in every log message through pattern layout placeholders. This article walks you through the relevant conversion characters, XML and properties-file configurations, and the programmatic use of MDC (Mapped Diagnostic Context) to enrich your logs with thread identity.

Thread Placeholders in Log4j2 PatternLayout

Log4j2's PatternLayout supports two thread-related conversion characters:

  • %t -- prints the thread name (for example, main, pool-1-thread-3).
  • %T -- prints the thread ID, the numeric value returned by Thread.currentThread().getId().

You choose one or both depending on what is most useful. Thread names are human-readable, while thread IDs are guaranteed unique within a JVM.

log4j2.xml Configuration

Below is a minimal log4j2.xml that writes to both the console and a rolling file, including the thread name and thread ID in every line:

xml
1<?xml version="1.0" encoding="UTF-8"?>
2<Configuration status="WARN">
3  <Appenders>
4    <Console name="Console" target="SYSTEM_OUT">
5      <PatternLayout
6        pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] (tid:%T) %-5level %logger{36} - %msg%n"/>
7    </Console>
8
9    <RollingFile name="File"
10                 fileName="logs/app.log"
11                 filePattern="logs/app-%d{yyyy-MM-dd}.log.gz">
12      <PatternLayout
13        pattern="%d{ISO8601} [%t] (tid:%T) %-5level %logger{36} - %msg%n"/>
14      <Policies>
15        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
16      </Policies>
17    </RollingFile>
18  </Appenders>
19
20  <Loggers>
21    <Root level="info">
22      <AppenderRef ref="Console"/>
23      <AppenderRef ref="File"/>
24    </Root>
25  </Loggers>
26</Configuration>

A sample output line looks like this:

 
2025-09-23 06:23:43.741 [pool-1-thread-3] (tid:17) INFO  com.example.Worker - Processing order #42

log4j.properties Equivalent

If your project still uses the older Log4j 1.x properties format, the same placeholders work inside ConversionPattern:

properties
1log4j.rootLogger=INFO, console, file
2
3log4j.appender.console=org.apache.log4j.ConsoleAppender
4log4j.appender.console.layout=org.apache.log4j.PatternLayout
5log4j.appender.console.layout.ConversionPattern=%d [%t] (tid:%T) %-5p %c - %m%n
6
7log4j.appender.file=org.apache.log4j.RollingFileAppender
8log4j.appender.file.File=logs/app.log
9log4j.appender.file.layout=org.apache.log4j.PatternLayout
10log4j.appender.file.layout.ConversionPattern=%d [%t] (tid:%T) %-5p %c - %m%n

Note that %T for the numeric thread ID is available in Log4j2. In Log4j 1.x, only %t (thread name) is natively supported. If you need the numeric ID with 1.x, you should use MDC as described below.

Enriching Logs with MDC

The Mapped Diagnostic Context (MDC) lets you attach arbitrary key-value pairs to the current thread's logging context. This is especially useful when you want to log a request ID, user ID, or any custom identifier alongside the thread info.

java
1import org.apache.logging.log4j.LogManager;
2import org.apache.logging.log4j.Logger;
3import org.apache.logging.log4j.ThreadContext; // Log4j2 MDC
4
5public class OrderProcessor implements Runnable {
6    private static final Logger logger = LogManager.getLogger(OrderProcessor.class);
7
8    @Override
9    public void run() {
10        // Put custom values into the thread-local context
11        ThreadContext.put("threadId", String.valueOf(Thread.currentThread().getId()));
12        ThreadContext.put("requestId", java.util.UUID.randomUUID().toString());
13
14        logger.info("Starting order processing");
15        // ... business logic ...
16        logger.info("Order processing complete");
17
18        // Always clean up to avoid leaking context in thread pools
19        ThreadContext.clearAll();
20    }
21}

Reference the MDC values in your pattern with %X{key}:

xml
<PatternLayout
  pattern="%d [%t] [tid:%X{threadId}] [req:%X{requestId}] %-5level %logger{36} - %msg%n"/>

This approach is powerful because you can attach business context (order IDs, user names) that pure thread identifiers cannot provide.

Common Pitfalls

  • Forgetting to clear MDC in thread pools. Thread pool threads are reused. If you do not call ThreadContext.clearAll() at the end of a task, the next task on that thread inherits stale context values, producing misleading logs.
  • Confusing %t with %T. Using lowercase %t gives you the thread name (a string), while uppercase %T gives the numeric thread ID. Mixing them up leads to unexpected output or layout errors.
  • Assuming %T works in Log4j 1.x. The numeric thread ID placeholder %T was introduced in Log4j2. In Log4j 1.x you must use MDC or a custom pattern converter to log the numeric ID.
  • Not setting meaningful thread names. The default names (Thread-0, pool-1-thread-1) are not very descriptive. Use thread.setName("order-worker-3") or configure your ExecutorService with a custom ThreadFactory so that %t output is immediately useful.
  • Over-verbose patterns in high-throughput systems. Including every available placeholder (%t, %T, %X{...}, full class names) inflates log file sizes. Choose the fields you actually need for debugging and leave the rest out in production configurations.

Summary

  • Use %t in your PatternLayout to print the thread name and %T to print the numeric thread ID.
  • Configure these placeholders in log4j2.xml or log4j.properties inside the pattern / ConversionPattern attribute.
  • Use MDC (ThreadContext in Log4j2) to attach custom per-thread values like request IDs, then reference them with %X{key}.
  • Always call ThreadContext.clearAll() when a task finishes, especially in thread-pool environments, to prevent context leakage.
  • Choose only the thread identifiers you truly need in production to keep log volume manageable.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.