NullPointerException when Throwable's hashCode returns different values
bugduplicatelayoutswaiting-for-maintainer
## Description
When a Throwable is logged asynchronously and shortly after this changed such that the Throwable's hashCode returns a different calculated value, a NullPointerException is thrown in background by the asynchronous logger, and the line is not logged.
This happen's with ScalaTest. A failing test throws a ScalaTest exception. When the stack trace is shortened after logging, the ScalaTest exception recalculates the hashCode based on the changed stack trace.
## Configuration
**Version:** 2.26.0
**Operating system:** macOS 26.5.1
**JDK:** 26.0.1+8-34
## Logs
```
2026-06-11T05:25:09.861732Z Log4j2-TF-1-AsyncLogger[AsyncContext@41e986c9]-1 ERROR An exception occurred processing Appender file
java.lang.NullPointerException: Cannot read field "stackTrace" because "metadata" is null
at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderStackTraceElements(ThrowableStackTraceRenderer.java:151)
at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:101)
at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:77)
at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:56)
at org.apache.logging.log4j.core.pattern.ThrowablePatternConverter.format(ThrowablePatternConverter.java:130)
at org.apache.logging.log4j.core.layout.PatternLayout$NoFormatPatternSerializer.toSerializable(PatternLayout.java:354)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:251)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:237)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:227)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:220)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:211)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:160)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:133)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:124)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:88)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:807)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:765)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:741)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:729)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:108)
at org.apache.logging.log4j.core.async.AsyncLogger.actualAsyncLog(AsyncLogger.java:568)
at org.apache.logging.log4j.core.async.RingBufferLogEvent.execute(RingBufferLogEvent.java:172)
at org.apache.logging.log4j.core.async.RingBufferLogEventHandler4.onEvent(RingBufferLogEventHandler4.java:54)
at org.apache.logging.log4j.core.async.RingBufferLogEventHandler.onEvent(RingBufferLogEventHandler.java:32)
at org.apache.logging.log4j.core.async.RingBufferLogEventHandler4.onEvent(RingBufferLogEventHandler4.java:31)
at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:168)
at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
at java.base/java.lang.Thread.run(Thread.java:1516)
```
## Reproduction
Reproduced with Scala because the test starts log4j2 with it's own log4j2.xml.
```
scala - <<< '
//> using dep org.apache.logging.log4j:log4j-api:2.26.0
//> using dep org.apache.logging.log4j:log4j-core:2.26.0
//> using dep org.apache.logging.log4j:log4j-slf4j2-impl:2.26.0
//> using dep com.lmax:disruptor:3.4.4
import java.lang.System.setProperty
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.{Files, Path}
val config =
"""<?xml version="1.0" encoding="UTF-8"?>
|<configuration status="WARN">
| <appenders>
| <randomAccessFile
| name="file"
| fileName="/tmp/TestLog4jNullPointerException.log"
| append="false"
| immediateFlush="false"/>
| </appenders>
|
| <loggers>
| <root level="trace">
| <appenderRef level="trace" ref="file"/>
| </root>
| </loggers>
|</configuration>
|""".stripMargin
final class MyException extends Exception:
private var counter = 0
override def hashCode =
counter += 1
counter
val file: Path = Files.createTempFile("TestLog4jNullPointerException-", "xml")
try
Files.write(file, config.getBytes(UTF_8))
setProperty("log4j2.configurationFile", file.toString)
setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector")
val logger = org.apache.logging.log4j.LogManager.getLogger("TestLog4jNullPointerException")
val exception = new MyException
logger.info("TEST", exception)
finally
Files.deleteIfExists(file)
'
```
5 条评论