This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit c32e62a5b8b82741f1c103b0b7c735dc20c6e054
Author: opencode <[email protected]>
AuthorDate: Fri Oct 9 11:00:27 2026 +0200

    Fix NullPointerException in ChannelException.getMessage() without detail
    
    getMessage() built its result from new StringBuilder(super.getMessage()),
    which throws a NullPointerException when the exception was created without
    a detail message, for example through the public no-arg constructor.
    Since Throwable.toString() calls getMessage(), logging or otherwise
    stringifying such an instance failed with an NPE, masking the original
    failure. This is a latent robustness bug in the public API: all internal
    construction sites pass a message or a cause, but custom interceptors or
    error handlers may not.
    
    Use the class name as the base when the detail message is null, in line
    with Throwable.toString(). The formatting for instances that do have a
    message is unchanged.
---
 .../apache/catalina/tribes/ChannelException.java   |  6 ++-
 .../catalina/tribes/TestChannelException.java      | 61 ++++++++++++++++++++++
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/tribes/ChannelException.java 
b/java/org/apache/catalina/tribes/ChannelException.java
index b286a4d7f4..a8e652692d 100644
--- a/java/org/apache/catalina/tribes/ChannelException.java
+++ b/java/org/apache/catalina/tribes/ChannelException.java
@@ -93,7 +93,11 @@ public class ChannelException extends Exception {
      */
     @Override
     public String getMessage() {
-        StringBuilder buf = new StringBuilder(super.getMessage());
+        // The detail message is null when the exception is created without a
+        // message and without a cause. Use the class name as the base, in line
+        // with Throwable.toString(), to keep getMessage() null-safe
+        String base = super.getMessage();
+        StringBuilder buf = new StringBuilder(base != null ? base : 
getClass().getName());
         if (faultyMembers == null || faultyMembers.isEmpty()) {
             buf.append("; No faulty members identified.");
         } else {
diff --git a/test/org/apache/catalina/tribes/TestChannelException.java 
b/test/org/apache/catalina/tribes/TestChannelException.java
new file mode 100644
index 0000000000..9319fd8c43
--- /dev/null
+++ b/test/org/apache/catalina/tribes/TestChannelException.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.tribes.membership.MemberImpl;
+
+public class TestChannelException {
+
+    /**
+     * Verify that getMessage() and toString() work for exceptions created 
without a detail message, instead of
+     * throwing a NullPointerException because the base message is null.
+     *
+     * @throws Exception if the test experiences an unexpected error
+     */
+    @Test
+    public void testMessageWithoutDetail() throws Exception {
+        ChannelException e = new ChannelException();
+        String message = e.getMessage();
+        Assert.assertNotNull(message);
+        Assert.assertTrue(message, 
message.contains(ChannelException.class.getName()));
+        Assert.assertTrue(message, message.contains("No faulty members 
identified."));
+        Assert.assertNotNull(e.toString());
+
+        ChannelException withCause = new ChannelException(new 
IllegalStateException("root"));
+        Assert.assertTrue(withCause.getMessage(), 
withCause.getMessage().contains("root"));
+    }
+
+    /**
+     * Verify the unchanged formatting for a detail message and for faulty 
members.
+     *
+     * @throws Exception if the test experiences an unexpected error
+     */
+    @Test
+    public void testMessageWithDetail() throws Exception {
+        ChannelException e = new ChannelException("detail");
+        Assert.assertTrue(e.getMessage(), e.getMessage().startsWith("detail"));
+
+        e.addFaultyMember(new MemberImpl("10.0.0.1", 4000, -1), new 
Exception("failed"));
+        String message = e.getMessage();
+        Assert.assertTrue(message, message.startsWith("detail"));
+        Assert.assertTrue(message, message.contains("Faulty members:"));
+        Assert.assertTrue(message, message.contains("10.0.0.1"));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to