From 16b25823ab2c116e6c076f30b3c81e546ef147f7 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 20 Jul 2026 17:59:32 -0400 Subject: [PATCH 1/2] fix: null-check LoggerDynamicMBean addAppender instantiation OptionConverter.instantiateByClassName returns null when the class is missing or not an Appender. addAppender immediately called setName and NPE'd on invalid JMX addAppender class names. Skip attach and log an error instead. --- .../apache/log4j/jmx/LoggerDynamicMBean.java | 4 ++ .../log4j/jmx/LoggerDynamicMBeanTest.java | 63 +++++++++++++++++++ ...fix_logger_dynamic_mbean_null_appender.xml | 8 +++ 3 files changed, 75 insertions(+) create mode 100644 log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java create mode 100644 src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java index 7be0f070f5f..a2a86d78f41 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java @@ -67,6 +67,10 @@ void addAppender(final String appenderClass, final String appenderName) { cat.debug("addAppender called with " + appenderClass + ", " + appenderName); final Appender appender = (Appender) OptionConverter.instantiateByClassName(appenderClass, org.apache.log4j.Appender.class, null); + if (appender == null) { + cat.error("Could not instantiate appender class [" + appenderClass + "] for name [" + appenderName + "]."); + return; + } appender.setName(appenderName); logger.addAppender(appender); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java new file mode 100644 index 00000000000..735f6959bb6 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java @@ -0,0 +1,63 @@ +/* + * 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.log4j.jmx; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Enumeration; +import org.apache.log4j.Appender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Logger; +import org.junit.jupiter.api.Test; + +/** + * Regression for invalid JMX {@code addAppender} class names: instantiation can + * return null and must not NPE when calling {@code setName}. + */ +class LoggerDynamicMBeanTest { + + @Test + void addAppenderDoesNotNpeWhenClassCannotBeInstantiated() { + final Logger logger = Logger.getLogger("jmx.LoggerDynamicMBeanTest.invalid"); + final LoggerDynamicMBean mbean = new LoggerDynamicMBean(logger); + + assertDoesNotThrow(() -> mbean.addAppender("this.class.does.not.exist.MissingAppender", "should-not-attach")); + assertFalse(hasAppenderNamed(logger, "should-not-attach")); + } + + @Test + void addAppenderStillAttachesValidAppender() { + final Logger logger = Logger.getLogger("jmx.LoggerDynamicMBeanTest.valid"); + final LoggerDynamicMBean mbean = new LoggerDynamicMBean(logger); + + mbean.addAppender(ConsoleAppender.class.getName(), "console-jmx"); + assertTrue(hasAppenderNamed(logger, "console-jmx")); + } + + private static boolean hasAppenderNamed(final Logger logger, final String name) { + final Enumeration enumeration = logger.getAllAppenders(); + while (enumeration.hasMoreElements()) { + final Appender appender = (Appender) enumeration.nextElement(); + if (name.equals(appender.getName())) { + return true; + } + } + return false; + } +} diff --git a/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml b/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml new file mode 100644 index 00000000000..b47e24558e0 --- /dev/null +++ b/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml @@ -0,0 +1,8 @@ + + + + Fix NPE in log4j-1.2-api `LoggerDynamicMBean.addAppender` when the appender class cannot be instantiated + From e5e9108f44f5c80e36e72817ac3d386ebf5e349b Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 20 Jul 2026 18:00:06 -0400 Subject: [PATCH 2/2] changelog: set issue id to PR #4185 --- src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml b/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml index b47e24558e0..2e8967364c0 100644 --- a/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml +++ b/src/changelog/.2.x.x/fix_logger_dynamic_mbean_null_appender.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="fixed"> - + Fix NPE in log4j-1.2-api `LoggerDynamicMBean.addAppender` when the appender class cannot be instantiated