From 5a4fb08aa9375e7ca3a9b6fb42f1acd2a6bba01e Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Tue, 6 Oct 2026 09:00:00 +0000 Subject: [PATCH] Fix #13362: restore the java.util.logging root handlers after an invocation activateLogging() replaces the handlers of the java.util.logging root logger with SLF4JBridgeHandler and nothing put them back. When Maven runs inside another JVM (embedded, resident, ToolProvider), the root logger is that JVM's: its logging went to Maven's SLF4J, and once Maven's realm was closed every java.util.logging record failed with NoClassDefFoundError: org/slf4j/spi/LocationAwareLogger. invoke() now restores the handlers like it restores the system properties and the context class loader. Co-Authored-By: Claude Opus 5.5 --- .../maven/cling/invoker/LookupInvoker.java | 19 +++++++++++ .../cling/invoker/mvn/MavenInvokerTest.java | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index a89b08e5ab75..8f6ace0ee70c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -37,6 +37,7 @@ import java.util.Properties; import java.util.function.Consumer; import java.util.function.UnaryOperator; +import java.util.logging.Handler; import org.apache.maven.api.Constants; import org.apache.maven.api.ProtoSession; @@ -124,6 +125,8 @@ public final int invoke(InvokerRequest invokerRequest) { Properties oldProps = new Properties(); oldProps.putAll(System.getProperties()); ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); + // activateLogging() replaces them with SLF4JBridgeHandler, whose classes live in Maven's realm + Handler[] oldRootLoggerHandlers = java.util.logging.Logger.getLogger("").getHandlers(); try (C context = createContext(invokerRequest)) { if (contextConsumer != null) { contextConsumer.accept(context); @@ -147,6 +150,22 @@ public final int invoke(InvokerRequest invokerRequest) { } finally { Thread.currentThread().setContextClassLoader(oldCL); System.setProperties(oldProps); + restoreRootLoggerHandlers(oldRootLoggerHandlers); + } + } + + /** + * Puts back the handlers the {@code java.util.logging} root logger had before the invocation. When Maven runs inside + * another JVM, the root logger belongs to that JVM, and a {@code SLF4JBridgeHandler} left on it would route the + * host's logging to Maven and fail once Maven's class realm is closed. + */ + private static void restoreRootLoggerHandlers(Handler[] handlers) { + java.util.logging.Logger root = java.util.logging.Logger.getLogger(""); + for (Handler handler : root.getHandlers()) { + root.removeHandler(handler); + } + for (Handler handler : handlers) { + root.addHandler(handler); } } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index b1115a4dce4f..18c6f828506f 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -23,6 +23,8 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; @@ -37,6 +39,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -66,6 +69,37 @@ void defaultFs( invoke(cwd, userHome, List.of("verify"), List.of()); } + /** + * The invocation replaces the handlers of the {@code java.util.logging} root logger with {@code SLF4JBridgeHandler}; + * they must be back afterwards, as the root logger belongs to the JVM Maven runs in (GH-13362). + */ + @Test + void restoresJavaUtilLoggingRootHandlers( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) + throws Exception { + java.util.logging.Logger root = java.util.logging.Logger.getLogger(""); + Handler[] original = root.getHandlers(); + Handler marker = new ConsoleHandler(); + for (Handler handler : original) { + root.removeHandler(handler); + } + root.addHandler(marker); + try { + invoke(cwd, userHome, List.of("validate"), List.of()); + + assertArrayEquals(new Handler[] {marker}, root.getHandlers()); + } finally { + root.removeHandler(marker); + for (Handler handler : root.getHandlers()) { + root.removeHandler(handler); + } + for (Handler handler : original) { + root.addHandler(handler); + } + } + } + @Disabled("Enable it when fully moved to NIO2 with Path/Filesystem (ie MavenExecutionRequest)") @Test void jimFs() throws Exception {