diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml
index 4d75c9a38ca8..5f55a310c8bd 100644
--- a/apache-maven/src/assembly/component.xml
+++ b/apache-maven/src/assembly/component.xml
@@ -68,6 +68,7 @@ under the License.
*.cmd
*.conf
+ *.java
dos
diff --git a/apache-maven/src/assembly/maven/bin/JvmConfigParser.java b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java
new file mode 100644
index 000000000000..1d6360aac94a
--- /dev/null
+++ b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * Parses .mvn/jvm.config file for Windows batch scripts.
+ * This avoids the complexity of parsing special characters (pipes, quotes, etc.) in batch scripts.
+ *
+ * Usage: java JvmConfigParser.java
+ *
+ * Outputs: Single line with space-separated quoted arguments (safe for batch scripts)
+ */
+public class JvmConfigParser {
+ public static void main(String[] args) {
+ if (args.length != 2) {
+ System.err.println("Usage: java JvmConfigParser.java ");
+ System.exit(1);
+ }
+
+ Path jvmConfigPath = Paths.get(args[0]);
+ String mavenProjectBasedir = args[1];
+
+ if (!Files.exists(jvmConfigPath)) {
+ // No jvm.config file - output nothing
+ return;
+ }
+
+ try (Stream lines = Files.lines(jvmConfigPath, StandardCharsets.UTF_8)) {
+ StringBuilder result = new StringBuilder();
+
+ lines.forEach(line -> {
+ // Remove comments
+ int commentIndex = line.indexOf('#');
+ if (commentIndex >= 0) {
+ line = line.substring(0, commentIndex);
+ }
+
+ // Trim whitespace
+ line = line.trim();
+
+ // Skip empty lines
+ if (line.isEmpty()) {
+ return;
+ }
+
+ // Replace MAVEN_PROJECTBASEDIR placeholders
+ line = line.replace("${MAVEN_PROJECTBASEDIR}", mavenProjectBasedir);
+ line = line.replace("$MAVEN_PROJECTBASEDIR", mavenProjectBasedir);
+
+ // Parse line into individual arguments (split on spaces, respecting quotes)
+ List parsed = parseArguments(line);
+
+ // Append each argument quoted
+ for (String arg : parsed) {
+ if (result.length() > 0) {
+ result.append(' ');
+ }
+ result.append('"').append(arg).append('"');
+ }
+ });
+
+ System.out.print(result.toString());
+ } catch (IOException e) {
+ System.err.println("Error reading jvm.config: " + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Parse a line into individual arguments, respecting quoted strings.
+ * Quotes are stripped from the arguments.
+ */
+ private static List parseArguments(String line) {
+ List args = new ArrayList<>();
+ StringBuilder current = new StringBuilder();
+ boolean inQuotes = false;
+ boolean inSingleQuotes = false;
+
+ for (int i = 0; i < line.length(); i++) {
+ char c = line.charAt(i);
+
+ if (c == '"' && !inSingleQuotes) {
+ inQuotes = !inQuotes;
+ // Don't include the quote character itself
+ } else if (c == '\'' && !inQuotes) {
+ inSingleQuotes = !inSingleQuotes;
+ // Don't include the quote character itself
+ } else if (c == ' ' && !inQuotes && !inSingleQuotes) {
+ // Space outside quotes - end of argument
+ if (current.length() > 0) {
+ args.add(current.toString());
+ current.setLength(0);
+ }
+ } else {
+ current.append(c);
+ }
+ }
+
+ // Add last argument
+ if (current.length() > 0) {
+ args.add(current.toString());
+ }
+
+ return args;
+ }
+}
+
diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn
index 8559d47af557..7ec4b77cb982 100755
--- a/apache-maven/src/assembly/maven/bin/mvn
+++ b/apache-maven/src/assembly/maven/bin/mvn
@@ -168,28 +168,48 @@ find_file_argument_basedir() {
# concatenates all lines of a file and replaces variables
concat_lines() {
if [ -f "$1" ]; then
- # First convert all CR to LF using tr
+ # Convert CR to LF, remove empty lines and comments, perform variable substitution,
+ # and escape pipe symbols for eval
tr '\r' '\n' < "$1" | \
sed -e '/^$/d' -e 's/#.*$//' | \
- # Replace LF with NUL for xargs
- tr '\n' '\0' | \
- # Split into words and process each argument
- # Use -0 with NUL to avoid special behaviour on quotes
- xargs -n 1 -0 | \
- while read -r arg; do
- # Replace variables first
- arg=$(echo "$arg" | sed \
- -e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
- -e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g")
-
- echo "$arg"
- done | \
- tr '\n' ' '
+ sed \
+ -e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
+ -e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g" | \
+ awk '
+ {
+ result = ""
+ in_quotes = 0
+ for (i = 1; i <= length($0); i++) {
+ char = substr($0, i, 1)
+ if (char == "\"") {
+ in_quotes = !in_quotes
+ result = result char
+ } else if (char == "|" && !in_quotes) {
+ # Escape unquoted pipes for eval
+ result = result "\\|"
+ } else {
+ result = result char
+ }
+ }
+ # Accumulate lines with space separator
+ if (NR > 1) printf " "
+ printf "%s", result
+ }
+ END { if (NR > 0) print "" }
+ '
fi
}
MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`"
-MAVEN_OPTS="$MAVEN_OPTS `concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
+# Read JVM config and append to MAVEN_OPTS, preserving special characters
+_jvm_config="`concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
+if [ -n "$_jvm_config" ]; then
+ if [ -n "$MAVEN_OPTS" ]; then
+ MAVEN_OPTS="$MAVEN_OPTS $_jvm_config"
+ else
+ MAVEN_OPTS="$_jvm_config"
+ fi
+fi
LAUNCHER_JAR=`echo "$MAVEN_HOME"/boot/plexus-classworlds-*.jar`
LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher
@@ -239,6 +259,7 @@ handle_args() {
handle_args "$@"
MAVEN_MAIN_CLASS=${MAVEN_MAIN_CLASS:=org.apache.maven.cling.MavenCling}
+# Build command string for eval
cmd="\"$JAVACMD\" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
@@ -251,13 +272,10 @@ cmd="\"$JAVACMD\" \
\"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\" \
$LAUNCHER_CLASS \
$MAVEN_ARGS"
+
# Add remaining arguments with proper quoting
for arg in "$@"; do
cmd="$cmd \"$arg\""
done
-# Debug: print the command that will be executed
-#echo "About to execute:"
-#echo "$cmd"
-
eval exec "$cmd"
diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd
index a3e8600df3d1..43c2be68c199 100644
--- a/apache-maven/src/assembly/maven/bin/mvn.cmd
+++ b/apache-maven/src/assembly/maven/bin/mvn.cmd
@@ -177,38 +177,33 @@ cd /d "%EXEC_DIR%"
:endDetectBaseDir
+rem Initialize JVM_CONFIG_MAVEN_OPTS to empty to avoid inheriting from environment
+set JVM_CONFIG_MAVEN_OPTS=
+
if not exist "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadJvmConfig
-@setlocal EnableExtensions EnableDelayedExpansion
-set JVM_CONFIG_MAVEN_OPTS=
-for /F "usebackq tokens=* delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do (
- set "line=%%a"
-
- rem Skip empty lines and full-line comments
- echo !line! | findstr /b /r /c:"[ ]*#" >nul
- if errorlevel 1 (
- rem Handle end-of-line comments by taking everything before #
- for /f "tokens=1* delims=#" %%i in ("!line!") do set "line=%%i"
-
- rem Trim leading/trailing spaces while preserving spaces in quotes
- set "trimmed=!line!"
- for /f "tokens=* delims= " %%i in ("!trimmed!") do set "trimmed=%%i"
- for /l %%i in (1,1,100) do if "!trimmed:~-1!"==" " set "trimmed=!trimmed:~0,-1!"
-
- rem Replace MAVEN_PROJECTBASEDIR placeholders
- set "trimmed=!trimmed:${MAVEN_PROJECTBASEDIR}=%MAVEN_PROJECTBASEDIR%!"
- set "trimmed=!trimmed:$MAVEN_PROJECTBASEDIR=%MAVEN_PROJECTBASEDIR%!"
-
- if not "!trimmed!"=="" (
- if "!JVM_CONFIG_MAVEN_OPTS!"=="" (
- set "JVM_CONFIG_MAVEN_OPTS=!trimmed!"
- ) else (
- set "JVM_CONFIG_MAVEN_OPTS=!JVM_CONFIG_MAVEN_OPTS! !trimmed!"
- )
- )
- )
-)
-@endlocal & set JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%
+rem Use Java to parse jvm.config to avoid batch script parsing issues with special characters
+rem This handles pipes, quotes, and other special characters correctly
+rem Use random temp directory to avoid conflicts between different Maven versions
+set "JVM_CONFIG_PARSER_DIR=%TEMP%\mvn-jvm-parser-%RANDOM%-%RANDOM%"
+mkdir "%JVM_CONFIG_PARSER_DIR%"
+set "JVM_CONFIG_TEMP=%TEMP%\mvn-jvm-config-%RANDOM%.txt"
+set "JVM_CONFIG_ERR=%TEMP%\mvn-jvm-config-err-%RANDOM%.txt"
+"%JAVACMD:java.exe=javac.exe%" -d "%JVM_CONFIG_PARSER_DIR%" "%MAVEN_HOME%\bin\JvmConfigParser.java" >nul 2>&1
+"%JAVACMD%" -cp "%JVM_CONFIG_PARSER_DIR%" JvmConfigParser "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" > "%JVM_CONFIG_TEMP%" 2> "%JVM_CONFIG_ERR%"
+rem Read the single line from temp file
+set /p JVM_CONFIG_MAVEN_OPTS=<"%JVM_CONFIG_TEMP%"
+
+rem Debug output to file for IT verification
+echo JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS% > "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt"
+echo MAVEN_OPTS=%MAVEN_OPTS% >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt"
+echo PARSER_STDERR: >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt"
+type "%JVM_CONFIG_ERR%" >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt" 2>nul
+
+rem Cleanup temp files and directory
+del "%JVM_CONFIG_TEMP%" 2>nul
+del "%JVM_CONFIG_ERR%" 2>nul
+rmdir /s /q "%JVM_CONFIG_PARSER_DIR%" 2>nul
:endReadJvmConfig
@@ -286,4 +281,4 @@ if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
-exit /b %ERROR_CODE%
+exit /b %ERROR_CODE%
\ No newline at end of file
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java
new file mode 100644
index 000000000000..95e0bba2d85e
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.it;
+
+import java.nio.file.Path;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for gh-11363:
+ * Verify that pipe symbols in .mvn/jvm.config are properly handled and don't cause shell command parsing errors.
+ */
+public class MavenITgh11363PipeSymbolsInJvmConfigTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that pipe symbols in .mvn/jvm.config are properly handled
+ */
+ @Test
+ void testPipeSymbolsInJvmConfig() throws Exception {
+ Path basedir = extractResources("/gh-11363-pipe-symbols-jvm-config")
+ .getAbsoluteFile()
+ .toPath();
+
+ Verifier verifier = newVerifier(basedir.toString());
+ verifier.setForkJvm(true); // Use forked JVM to test .mvn/jvm.config processing
+ verifier.addCliArguments("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ assertEquals("de|*.de|my.company.mirror.de", props.getProperty("project.properties.pom.prop.nonProxyHosts"));
+ assertEquals("value|with|pipes", props.getProperty("project.properties.pom.prop.with.pipes"));
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config
new file mode 100644
index 000000000000..fa129e3da219
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config
@@ -0,0 +1,3 @@
+# Test for MNG-11363: Maven 4 fails to parse pipe symbols in .mvn/jvm.config
+-Dhttp.nonProxyHosts=de|*.de|my.company.mirror.de
+-Dprop.with.pipes="value|with|pipes"
diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml
new file mode 100644
index 000000000000..52f90ad94181
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.mng11363
+ test
+ 1.0
+
+ Maven Integration Test :: MNG-11363
+ Verify that JVM args can contain pipe symbols in .mvn/jvm.config.
+
+
+ ${http.nonProxyHosts}
+ ${prop.with.pipes}
+
+
+
+
+
+ org.apache.maven.its.plugins
+ maven-it-plugin-expression
+ 2.1-SNAPSHOT
+
+
+ test
+
+ eval
+
+ validate
+
+ target/pom.properties
+
+ project/properties
+
+
+
+
+
+
+
+