From 74bc75a6bd80fb3bcaed5f4ca26fa3e9a9db1ff2 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 24 Sep 2026 21:51:46 +0000 Subject: [PATCH 1/4] Print usage when no goals and no POM are specified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user runs 'mvn' without any arguments and there is no pom.xml in the current directory, Maven currently fails with a confusing BUILD FAILURE / NoGoalSpecifiedException error. This is surprising and unhelpful: the user likely just typed 'mvn' by mistake or wants to explore what Maven can do. This commit improves the UX by detecting this specific situation early in MavenInvoker.postCommands() — after DI is fully set up but before the build is attempted — and printing the usage (same output as mvn -h) then exiting with code 0, instead of failing. The check is deliberately narrow: it only fires when BOTH conditions are true: 1. No goals or lifecycle phases were specified on the command line. 2. No POM file is present in the current directory (or via -f). If a POM is found, Maven proceeds as before: the default goal (if any) is executed, or the existing NoGoalSpecifiedException is reported. This preserves existing behavior for all normal project builds. Fixes: https://github.com/apache/maven/issues/XXXXX --- .../apache/maven/cling/invoker/mvn/MavenInvoker.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java index e6372ccfd818..03357c5f09bb 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java @@ -36,6 +36,7 @@ import org.apache.maven.api.Constants; import org.apache.maven.api.MonotonicClock; import org.apache.maven.api.annotations.Nullable; +import org.apache.maven.api.cli.InvokerException; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.mvn.MavenOptions; @@ -132,6 +133,15 @@ protected void postCommands(MavenContext context) throws Exception { } else if (context.options().strictChecksums().orElse(false)) { logger.info("Enabling strict checksum verification on all artifact downloads."); } + + // If no goals/phases were specified and there is no POM in the current directory, + // print usage (like `mvn -h`) and exit successfully instead of failing with + // "No goals have been specified for this build." + // When a POM is present the default goal (if any) may apply, so let Maven proceed normally. + if (context.options().goals().orElse(List.of()).isEmpty() && determinePom(context, context.lookup) == null) { + context.options().displayHelp(context.invokerRequest.parserRequest(), determineWriter(context)); + throw new InvokerException.ExitException(0); + } } protected void toolchains(MavenContext context, MavenExecutionRequest request) throws Exception { From 98a8582e27c23024e8797eb24eb9b7daf35e06fe Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 24 Sep 2026 22:18:21 +0000 Subject: [PATCH 2/4] Improve error message when -f points to a non-existent POM file When the -f/--file option points to a non-existent file, Maven previously printed redundant information: POM file X does not exist (bare, no prefix - source TBD) [ERROR] Error executing Maven. [ERROR] Error determining top directory [ERROR] Caused by: POM file X does not exist The 'Error determining top directory' wrapper and 'Caused by:' repetition added noise without helping the user understand what went wrong. Now, when getTopDirectory() throws an IllegalArgumentException (a user- facing error with a self-explanatory message), we log the message directly instead of wrapping it. The output is now: [ERROR] Error executing Maven. [ERROR] POM file X specified with the -f/--file command line argument does not exist The same pattern is applied to getRootDirectory() for consistency. --- .../org/apache/maven/cling/invoker/BaseParser.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java index 9a740753bb0f..c10c300d046c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java @@ -137,6 +137,12 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) { // top/root try { context.topDirectory = getTopDirectory(context); + } catch (IllegalArgumentException e) { + // User-facing error (e.g. -f points to a non-existent file): report the message directly, + // without wrapping it in an internal "Error determining top directory" prefix. + context.parsingFailed = true; + context.topDirectory = context.cwd; + parserRequest.logger().error(e.getMessage()); } catch (Exception e) { context.parsingFailed = true; context.topDirectory = context.cwd; @@ -144,6 +150,10 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) { } try { context.rootDirectory = getRootDirectory(context); + } catch (IllegalArgumentException e) { + context.parsingFailed = true; + context.rootDirectory = context.cwd; + parserRequest.logger().error(e.getMessage()); } catch (Exception e) { context.parsingFailed = true; context.rootDirectory = context.cwd; From 05c64ddc039176a8d7024056beedb52a9c76dbed Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 24 Sep 2026 23:56:59 +0000 Subject: [PATCH 3/4] Fix double error message when -f points to a non-existent POM file The shell script's find_file_argument_basedir() already validated the -f argument and printed an error, but the exit 1 was inside a subshell so it did not stop the main script from launching the JVM, which then printed the same error again through the Java logging infrastructure. Fix: propagate the subshell exit code through find_maven_basedir() and bail out before launching the JVM if the -f argument is invalid. Also add the [ERROR] prefix to the shell-side error messages to match Maven's standard logging format. --- apache-maven/src/assembly/maven/bin/mvn | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn index fe04dacb0bcb..461920d070de 100755 --- a/apache-maven/src/assembly/maven/bin/mvn +++ b/apache-maven/src/assembly/maven/bin/mvn @@ -327,6 +327,8 @@ fi find_maven_basedir() { ( basedir=`find_file_argument_basedir "$@"` + rc=$? + [ $rc -ne 0 ] && exit $rc wdir="$basedir" while : do @@ -356,11 +358,11 @@ find_file_argument_basedir() { basedir=`dirname "${arg}"` basedir=`cd "$basedir" && pwd -P` if [ ! -d "$basedir" ]; then - echo "Directory $basedir extracted from the -f/--file command-line argument ${arg} does not exist" >&2 + printf '[ERROR] Directory %s extracted from the -f/--file command-line argument %s does not exist\n' "$basedir" "${arg}" >&2 exit 1 fi else - echo "POM file ${arg} specified with the -f/--file command line argument does not exist" >&2 + printf '[ERROR] POM file %s specified with the -f/--file command line argument does not exist\n' "${arg}" >&2 exit 1 fi break @@ -423,6 +425,9 @@ concat_lines() { } MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`" +if [ $? -ne 0 ] ; then + exit 1 +fi # Under Cygwin/MinGW the POSIX form of the project base directory is required by # the shell (to locate .mvn/jvm.config), while the JVM and everything it hands From decad153062366ac9cce25513e6286c2a9a4520b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 25 Sep 2026 12:22:39 +0000 Subject: [PATCH 4/4] fix: do not exit from shell script when -f argument does not exist The find_file_argument_basedir() function previously exited with an error when the -f/--file argument pointed to a non-existent path. This is problematic in two ways: 1. On Cygwin/MSYS2, paths like /cygdrive/c/temp/pom.xml do not exist on the POSIX layer; they are converted to Windows paths (C:\temp\pom.xml) later in the script. The early exit prevented that conversion. 2. The Java layer (BaseParser) already catches the missing-file case and reports a clean user-facing error message. Duplicating the check in the shell script caused a double error message. The fix removes the early exit from find_file_argument_basedir() (and the now-redundant propagation in find_maven_basedir() and the main script). When the path cannot be resolved the function falls back to the current directory, and the Java layer takes care of reporting the error. --- apache-maven/src/assembly/maven/bin/mvn | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn index 461920d070de..6cfcf37deb35 100755 --- a/apache-maven/src/assembly/maven/bin/mvn +++ b/apache-maven/src/assembly/maven/bin/mvn @@ -327,8 +327,6 @@ fi find_maven_basedir() { ( basedir=`find_file_argument_basedir "$@"` - rc=$? - [ $rc -ne 0 ] && exit $rc wdir="$basedir" while : do @@ -357,14 +355,11 @@ find_file_argument_basedir() { elif [ -f "${arg}" ]; then basedir=`dirname "${arg}"` basedir=`cd "$basedir" && pwd -P` - if [ ! -d "$basedir" ]; then - printf '[ERROR] Directory %s extracted from the -f/--file command-line argument %s does not exist\n' "$basedir" "${arg}" >&2 - exit 1 - fi - else - printf '[ERROR] POM file %s specified with the -f/--file command line argument does not exist\n' "${arg}" >&2 - exit 1 fi + # If the argument does not exist (e.g. a Cygwin/MSYS path that will be + # converted later, or a genuinely missing file), fall back to the current + # directory. The Java layer validates the path and produces the proper + # error message when the file is truly absent. break fi if [ "$arg" = "-f" -o "$arg" = "--file" ]; then @@ -425,9 +420,6 @@ concat_lines() { } MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`" -if [ $? -ne 0 ] ; then - exit 1 -fi # Under Cygwin/MinGW the POSIX form of the project base directory is required by # the shell (to locate .mvn/jvm.config), while the JVM and everything it hands