You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/28 13:04:21 UTC
(maven) branch backport/12875-to-4.0.x created (now cad483cd40)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a change to branch backport/12875-to-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
at cad483cd40 Fix #2520: default maven.mainClass in Java instead of classworlds config (#12875)
This branch includes the following new commits:
new cad483cd40 Fix #2520: default maven.mainClass in Java instead of classworlds config (#12875)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven) 01/01: Fix #2520: default maven.mainClass in Java instead of classworlds config (#12875)
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch backport/12875-to-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
commit cad483cd40472689742e7a6db1b8819a11f48087
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Fri Aug 28 15:03:51 2026 +0200
Fix #2520: default maven.mainClass in Java instead of classworlds config (#12875)
Move the main class defaulting from classworlds property resolution to
Java code. MavenCling is now hardcoded as the entry point in m2.conf
and delegates to the class specified by -Dmaven.mainClass via
reflection when set. This fixes the Maven Wrapper and IDE integration
by removing the dependency on classworlds resolving ${maven.mainClass}.
Co-authored-by: Claude Opus 4.6 <no...@anthropic.com>
---
apache-maven/src/assembly/maven/bin/m2.conf | 4 +-
.../java/org/apache/maven/cling/MavenCling.java | 47 +++++++++
.../org/apache/maven/cling/MavenClingTest.java | 117 +++++++++++++++++++++
3 files changed, 165 insertions(+), 3 deletions(-)
diff --git a/apache-maven/src/assembly/maven/bin/m2.conf b/apache-maven/src/assembly/maven/bin/m2.conf
index b91431dea5..e1ec455bcf 100644
--- a/apache-maven/src/assembly/maven/bin/m2.conf
+++ b/apache-maven/src/assembly/maven/bin/m2.conf
@@ -16,9 +16,7 @@
# specific language governing permissions and limitations
# under the License.
-set maven.mainClass default org.apache.maven.cling.MavenCling
-
-main is ${maven.mainClass} from plexus.core
+main is org.apache.maven.cling.MavenCling from plexus.core
set maven.conf default ${maven.home}/conf
set maven.installation.conf default ${maven.conf}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
index 65fa91e78e..f7e5593198 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
@@ -21,6 +21,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.Invoker;
@@ -33,8 +35,18 @@
/**
* Maven CLI "new-gen".
+ * <p>
+ * This is the default entry point for Maven. When launched via ClassWorlds, it checks the
+ * {@code maven.mainClass} system property and delegates to the specified class if set.
+ * This allows external tools (Maven Wrapper, IDEs) to launch Maven without setting
+ * the main class property — {@code MavenCling} is used as the default.
*/
public class MavenCling extends ClingSupport {
+ /**
+ * System property used by launcher scripts to select the CLI implementation.
+ */
+ static final String MAVEN_MAIN_CLASS_PROPERTY = "maven.mainClass";
+
/**
* "Normal" Java entry point. Note: Maven uses ClassWorld Launcher and this entry point is NOT used under normal
* circumstances.
@@ -46,11 +58,46 @@ public static void main(String[] args) throws IOException {
/**
* ClassWorld Launcher "enhanced" entry point: returning exitCode and accepts Class World.
+ * <p>
+ * When the {@code maven.mainClass} system property is set to a class other than {@code MavenCling},
+ * this method delegates to that class's {@code main(String[], ClassWorld)} method via reflection.
+ * This makes {@code MavenCling} the default entry point that external tools (such as the Maven Wrapper
+ * or IDEs) can rely on without needing to set the {@code maven.mainClass} property.
*/
public static int main(String[] args, ClassWorld world) throws IOException {
+ String mainClass = System.getProperty(MAVEN_MAIN_CLASS_PROPERTY);
+ if (mainClass != null && !MavenCling.class.getName().equals(mainClass)) {
+ return delegateMain(mainClass, args, world);
+ }
return new MavenCling(world).run(args, null, null, null, false);
}
+ private static int delegateMain(String mainClass, String[] args, ClassWorld world) throws IOException {
+ try {
+ Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(mainClass);
+ Method method = clazz.getMethod("main", String[].class, ClassWorld.class);
+ return (int) method.invoke(null, args, world);
+ } catch (ClassNotFoundException e) {
+ throw new IOException("Cannot find maven.mainClass: " + mainClass, e);
+ } catch (NoSuchMethodException e) {
+ throw new IOException("maven.mainClass does not have main(String[], ClassWorld) method: " + mainClass, e);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new IOException("Failed to invoke maven.mainClass: " + mainClass, cause != null ? cause : e);
+ } catch (IllegalAccessException e) {
+ throw new IOException("Cannot access main method of maven.mainClass: " + mainClass, e);
+ }
+ }
+
/**
* ClassWorld Launcher "embedded" entry point: returning exitCode and accepts Class World and streams.
*/
diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/MavenClingTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/MavenClingTest.java
new file mode 100644
index 0000000000..e59e52a1de
--- /dev/null
+++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/MavenClingTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.maven.cling;
+
+import java.io.IOException;
+
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link MavenCling} main class dispatching.
+ */
+class MavenClingTest {
+
+ /**
+ * When {@code maven.mainClass} is set to a non-existent class, the delegation should throw
+ * an {@link IOException} with a descriptive message.
+ */
+ @Test
+ void delegatesToUnknownClassThrowsIOException() {
+ String original = System.getProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ try {
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, "com.example.NonExistentClass");
+ ClassWorld world =
+ new ClassWorld(ClingSupport.CORE_CLASS_REALM_ID, getClass().getClassLoader());
+ IOException ex = assertThrows(IOException.class, () -> MavenCling.main(new String[0], world));
+ assertEquals("Cannot find maven.mainClass: com.example.NonExistentClass", ex.getMessage());
+ } finally {
+ if (original != null) {
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, original);
+ } else {
+ System.clearProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ }
+ }
+ }
+
+ /**
+ * When {@code maven.mainClass} is set to a class that exists but does not have the
+ * {@code main(String[], ClassWorld)} method, delegation should throw an {@link IOException}.
+ */
+ @Test
+ void delegatesToClassWithoutMainMethodThrowsIOException() {
+ String original = System.getProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ try {
+ // String.class exists but has no main(String[], ClassWorld)
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, "java.lang.String");
+ ClassWorld world =
+ new ClassWorld(ClingSupport.CORE_CLASS_REALM_ID, getClass().getClassLoader());
+ IOException ex = assertThrows(IOException.class, () -> MavenCling.main(new String[0], world));
+ assertEquals(
+ "maven.mainClass does not have main(String[], ClassWorld) method: java.lang.String",
+ ex.getMessage());
+ } finally {
+ if (original != null) {
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, original);
+ } else {
+ System.clearProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ }
+ }
+ }
+
+ /**
+ * When {@code maven.mainClass} is set to a valid class with the correct entry point,
+ * delegation should invoke that class's main method and return its exit code.
+ */
+ @Test
+ void delegatesToValidClass() throws IOException {
+ String original = System.getProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ try {
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, MavenClingTest.DelegateTarget.class.getName());
+ ClassWorld world =
+ new ClassWorld(ClingSupport.CORE_CLASS_REALM_ID, getClass().getClassLoader());
+ DelegateTarget.invoked = false;
+ int exitCode = MavenCling.main(new String[] {"test-arg"}, world);
+ assertEquals(42, exitCode);
+ assertEquals(true, DelegateTarget.invoked);
+ } finally {
+ if (original != null) {
+ System.setProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY, original);
+ } else {
+ System.clearProperty(MavenCling.MAVEN_MAIN_CLASS_PROPERTY);
+ }
+ }
+ }
+
+ /**
+ * A test class that serves as a delegation target for {@link MavenCling}.
+ */
+ public static class DelegateTarget {
+ static boolean invoked;
+
+ @SuppressWarnings("unused")
+ public static int main(String[] args, ClassWorld world) {
+ invoked = true;
+ return 42;
+ }
+ }
+}