diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
index ea9294e58a4..f938c26e9e4 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
@@ -19,6 +19,7 @@
import static java.util.Arrays.asList;
import static org.apache.logging.log4j.util.Strings.LINE_SEPARATOR;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
import foo.TestFriendlyException;
import java.io.ByteArrayOutputStream;
@@ -384,6 +385,101 @@ void depth_and_package_limited_output_should_match_2(final DepthTestCase depthTe
" ... 3 more",
"Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]"));
}
+
+ /**
+ * Verifies that the extended stack trace renderer does not propagate
+ * {@link Throwable} when a class in the stack trace cannot be resolved.
+ *
+ * @see #4028
+ */
+ @Test
+ @Issue("https://github.com/apache/logging-log4j2/issues/4028")
+ void rendering_should_succeed_with_nonexistent_class_in_stack_trace() {
+
+ // Create an exception with a stack trace element referencing a non-existent class
+ final Exception exception = new Exception("test exception");
+ final StackTraceElement[] originalTrace = exception.getStackTrace();
+ final StackTraceElement[] modifiedTrace = new StackTraceElement[originalTrace.length + 1];
+ modifiedTrace[0] = new StackTraceElement(
+ "com.nonexistent.deliberately.missing.ClassName",
+ "someMethod",
+ "ClassName.java",
+ 42);
+ System.arraycopy(originalTrace, 0, modifiedTrace, 1, originalTrace.length);
+ exception.setStackTrace(modifiedTrace);
+
+ // Create the formatter using patternPrefix (e.g., "%xEx" for extended)
+ final List patternFormatters =
+ PATTERN_PARSER.parse(patternPrefix, false, true, true);
+ assertThat(patternFormatters).hasSize(1);
+ final PatternFormatter patternFormatter = patternFormatters.get(0);
+
+ final LogEvent logEvent = Log4jLogEvent.newBuilder()
+ .setThrown(exception)
+ .setLevel(LEVEL)
+ .build();
+ final StringBuilder buffer = new StringBuilder();
+
+ // The rendering should not throw any exception
+ assertThatNoException().isThrownBy(() -> patternFormatter.format(logEvent, buffer));
+
+ // The output should contain our non-existent class name and the exception message
+ final String output = buffer.toString();
+ assertThat(output).contains("com.nonexistent.deliberately.missing.ClassName");
+ assertThat(output).contains("test exception");
+ }
+
+ /**
+ * Verifies that the stack trace renderer gracefully handles a class loader
+ * that throws {@link NoClassDefFoundError} during class resolution.
+ *
+ * @see #4028
+ */
+ @Test
+ @Issue("https://github.com/apache/logging-log4j2/issues/4028")
+ void rendering_should_succeed_when_classloader_throws_linkage_error() {
+
+ // Create a class loader that always throws NoClassDefFoundError
+ final ClassLoader errorThrowingLoader = new ClassLoader(null) {
+ @Override
+ public Class> loadClass(final String name) throws ClassNotFoundException {
+ throw new NoClassDefFoundError("Simulated missing class: " + name);
+ }
+ };
+
+ final Exception exception = new Exception("test with custom classloader");
+ exception.setStackTrace(new StackTraceElement[] {
+ new StackTraceElement(
+ "com.example.UnloadedClass", "doWork", "UnloadedClass.java", 10),
+ new StackTraceElement(
+ "com.example.CallerClass", "invoke", "CallerClass.java", 20)
+ });
+
+ final List patternFormatters =
+ PATTERN_PARSER.parse(patternPrefix, false, true, true);
+ assertThat(patternFormatters).hasSize(1);
+ final PatternFormatter patternFormatter = patternFormatters.get(0);
+
+ final LogEvent logEvent = Log4jLogEvent.newBuilder()
+ .setThrown(exception)
+ .setLevel(LEVEL)
+ .build();
+ final StringBuilder buffer = new StringBuilder();
+
+ // Set the error-throwing class loader as the context class loader
+ final Thread currentThread = Thread.currentThread();
+ final ClassLoader originalLoader = currentThread.getContextClassLoader();
+ try {
+ currentThread.setContextClassLoader(errorThrowingLoader);
+ assertThatNoException().isThrownBy(() -> patternFormatter.format(logEvent, buffer));
+ } finally {
+ currentThread.setContextClassLoader(originalLoader);
+ }
+
+ final String output = buffer.toString();
+ assertThat(output).contains("test with custom classloader");
+ assertThat(output).contains("com.example.UnloadedClass");
+ }
}
abstract static class AbstractStackTraceTest {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableExtendedStackTraceRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableExtendedStackTraceRenderer.java
index c70e57d4d46..2c7ea334bb1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableExtendedStackTraceRenderer.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableExtendedStackTraceRenderer.java
@@ -181,7 +181,7 @@ private static Class> loadClass(final ClassLoader loader, final String classNa
if (clazz != null) {
return clazz;
}
- } catch (final Exception ignored) {
+ } catch (final Throwable ignored) {
// Do nothing
}
}
diff --git a/src/changelog/.2.x.x/4049_fix_catch_LinkageError_in_ThrowableExtendedStackTraceRenderer.xml b/src/changelog/.2.x.x/4049_fix_catch_LinkageError_in_ThrowableExtendedStackTraceRenderer.xml
new file mode 100644
index 00000000000..f4828edda5e
--- /dev/null
+++ b/src/changelog/.2.x.x/4049_fix_catch_LinkageError_in_ThrowableExtendedStackTraceRenderer.xml
@@ -0,0 +1,9 @@
+
+
+
+ Ensure all `Throwable`s are handled while rendering stack traces
+