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]
