Summary
The Maven 4 mvn shell script uses eval exec "$cmd" to launch the JVM.
This causes the shell to expand ${...} patterns in user-provided CLI arguments,
breaking any argument that contains Maven property placeholders like
${surefire.threadNumber} or ${project.basedir}.
Maven 3's mvn script uses exec ... "$@" which passes arguments verbatim.
Reproducer
mvn validate -DtestProp='value_${some.placeholder}'
# Maven 3: works fine, passes the literal string to Java
# Maven 4: /path/to/mvn: line 302: bad substitution
Root cause
In apache-maven/src/assembly/maven/bin/mvn
(lines 278-302):
for arg in "$@"; do
cmd="$cmd \"$arg\""
done
eval exec "$cmd"
User arguments are concatenated into the cmd string.
When eval executes it, the shell re-parses the entire string and expands
${...} as shell variables.
Maven 3's approach (exec ... "$@") passes arguments directly without
re-interpretation.
Impact
This affects any Maven plugin that passes ${...} placeholders via -D
system properties on the command line. Known affected tests in
maven-surefire:
20 maven-surefire integration tests fail with bad substitution when run with
Maven 4. See also apache/maven-surefire#3345.
Proposed fix
Keep eval for the base command (needed for $MAVEN_OPTS word splitting)
but pass user arguments directly via "$@":
# before:
for arg in "$@"; do
cmd="$cmd \"$arg\""
done
eval exec "$cmd"
# after:
eval exec "$cmd" '"$@"'
Verified locally: all 20 bad substitution errors eliminated.
MAVEN_OPTS word splitting and arguments with spaces continue to work correctly.
Summary
The Maven 4
mvnshell script useseval exec "$cmd"to launch the JVM.This causes the shell to expand
${...}patterns in user-provided CLI arguments,breaking any argument that contains Maven property placeholders like
${surefire.threadNumber}or${project.basedir}.Maven 3's
mvnscript usesexec ... "$@"which passes arguments verbatim.Reproducer
Root cause
In
apache-maven/src/assembly/maven/bin/mvn(lines 278-302):
User arguments are concatenated into the
cmdstring.When
evalexecutes it, the shell re-parses the entire string and expands${...}as shell variables.Maven 3's approach (
exec ... "$@") passes arguments directly withoutre-interpretation.
Impact
This affects any Maven plugin that passes
${...}placeholders via-Dsystem properties on the command line. Known affected tests in
maven-surefire:
ForkCountIT.javaline 161 — passes-DtestProperty=testValue_${surefire.threadNumber}_${surefire.forkNumber}ForkCountMultiModuleIT.javaline 126 — same patternForkCountTestNGIT— extendsForkCountIT20 maven-surefire integration tests fail with
bad substitutionwhen run withMaven 4. See also apache/maven-surefire#3345.
Proposed fix
Keep
evalfor the base command (needed for$MAVEN_OPTSword splitting)but pass user arguments directly via
"$@":Verified locally: all 20
bad substitutionerrors eliminated.MAVEN_OPTSword splitting and arguments with spaces continue to work correctly.