Affected versions
Reproduced on both master (4.1.0-SNAPSHOT, 671031e) and maven-4.0.x tip (4.0.0-SNAPSHOT, e1f8234). On 4.0.0-rc-5 the symptom is masked by the launcher bug #11978 (eval → bad substitution), already fixed for rc-6 — this issue is about what happens once Maven actually runs.
Reproduction
Minimal pom-packaging project, then (quoted so the shell passes ${...} literally):
mvn deploy '-DaltDeploymentRepository=test::file://${project.build.directory}/deploy'
- Maven 3.9.16 (deploy-plugin pinned to 3.1.3 via direct goal invocation): resolves the expression, deploys to
target/deploy/… ✅
- Maven 4 (same deploy-plugin 3.1.3): the expression evaluates to the empty string → repository URL becomes
file:///deploy → Could not transfer artifact … from/to test (file:///deploy): /deploy: Read-only file system ❌
Further probes — all wiped to empty: ${project.build.directory}, ${project.basedir}, and even ${user.dir} (a plain system property).
Analysis
The values are destroyed at CLI parse time, before any project context exists:
BaseParser.populateUserProperties() interpolates all user-specified -D values against only the other user properties plus the small extraInterpolationSource() paths map — no system properties, no project context (impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java:441).
- It uses the
Interpolator.interpolate(Map, UnaryOperator) convenience overload, which defaults to defaultsToEmpty = true (api/maven-api-core/…/Interpolator.java:51-53), so DefaultInterpolator replaces every unresolvable placeholder with "" (impl/maven-impl/…/model/DefaultInterpolator.java:351-353).
- The later v3-compat
PluginParameterExpressionEvaluator would resolve these expressions fine (it merges user + system properties and recursively evaluates embedded ${...}), but it only ever sees the already-emptied value.
This is independent of (and complementary to) the recent launcher quoting fixes: #11978/#11983 fixed the shell layer so ${...} arguments arrive intact in the JVM — the failing IT below passes arguments programmatically via the Verifier (no shell involved) and still shows the wipe. Notably, on rc-5 the launcher bug masked this issue with a loud bad substitution shell error; with the launcher fix shipping in rc-6, users will instead hit this silent empty-substitution first.
The parse-time interpolation itself was introduced deliberately by #2480 ("User properties are not interpolated for paths") to support ${session.topDirectory}/${session.rootDirectory} in -D values — the regression is the destructive defaultsToEmpty=true for every other expression, and the asymmetry with the options interpolation (which does include systemProperties::get in its callback chain, BaseParser.java:178-184).
A minimal fix candidate: pass defaultsToEmpty=false for user-specified properties (leaving unresolved placeholders literal for later mojo-parameter evaluation), possibly plus systemProperties::get in the callback chain. Happy to turn that into a PR.
Impact
A common CI pattern (file-based staging deploy via altDeploymentRepository) breaks, and the failure mode is nasty: no error, Maven silently targets a wrong absolute path (/deploy). maven-assembly-plugin already had to work around this in its ITs (apache/maven-assembly-plugin#1325). Any -D-supplied plugin parameter whose value embeds ${...} is affected, not just deploy.
Test coverage
No existing core IT covers this. I have a failing IT ready to contribute (two cases: ${user.dir} and ${project.build.directory} in a CLI-supplied parameter, asserting Maven 3 semantics via maven-it-plugin-configuration); it fails on current master with expected: <PRE-…-POST> but was: <PRE--POST>. PR follows.
cc @gnodet — given the launcher fix lands in rc-6, this becomes the first user-visible failure mode for the ${...}-in--D pattern there.
Affected versions
Reproduced on both
master(4.1.0-SNAPSHOT, 671031e) andmaven-4.0.xtip (4.0.0-SNAPSHOT, e1f8234). On 4.0.0-rc-5 the symptom is masked by the launcher bug #11978 (eval→bad substitution), already fixed for rc-6 — this issue is about what happens once Maven actually runs.Reproduction
Minimal
pom-packaging project, then (quoted so the shell passes${...}literally):target/deploy/…✅file:///deploy→Could not transfer artifact … from/to test (file:///deploy): /deploy: Read-only file system❌Further probes — all wiped to empty:
${project.build.directory},${project.basedir}, and even${user.dir}(a plain system property).Analysis
The values are destroyed at CLI parse time, before any project context exists:
BaseParser.populateUserProperties()interpolates all user-specified-Dvalues against only the other user properties plus the smallextraInterpolationSource()paths map — no system properties, no project context (impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java:441).Interpolator.interpolate(Map, UnaryOperator)convenience overload, which defaults todefaultsToEmpty = true(api/maven-api-core/…/Interpolator.java:51-53), soDefaultInterpolatorreplaces every unresolvable placeholder with""(impl/maven-impl/…/model/DefaultInterpolator.java:351-353).PluginParameterExpressionEvaluatorwould resolve these expressions fine (it merges user + system properties and recursively evaluates embedded${...}), but it only ever sees the already-emptied value.This is independent of (and complementary to) the recent launcher quoting fixes: #11978/#11983 fixed the shell layer so
${...}arguments arrive intact in the JVM — the failing IT below passes arguments programmatically via the Verifier (no shell involved) and still shows the wipe. Notably, on rc-5 the launcher bug masked this issue with a loudbad substitutionshell error; with the launcher fix shipping in rc-6, users will instead hit this silent empty-substitution first.The parse-time interpolation itself was introduced deliberately by #2480 ("User properties are not interpolated for paths") to support
${session.topDirectory}/${session.rootDirectory}in-Dvalues — the regression is the destructivedefaultsToEmpty=truefor every other expression, and the asymmetry with the options interpolation (which does includesystemProperties::getin its callback chain,BaseParser.java:178-184).A minimal fix candidate: pass
defaultsToEmpty=falsefor user-specified properties (leaving unresolved placeholders literal for later mojo-parameter evaluation), possibly plussystemProperties::getin the callback chain. Happy to turn that into a PR.Impact
A common CI pattern (file-based staging deploy via
altDeploymentRepository) breaks, and the failure mode is nasty: no error, Maven silently targets a wrong absolute path (/deploy). maven-assembly-plugin already had to work around this in its ITs (apache/maven-assembly-plugin#1325). Any-D-supplied plugin parameter whose value embeds${...}is affected, not just deploy.Test coverage
No existing core IT covers this. I have a failing IT ready to contribute (two cases:
${user.dir}and${project.build.directory}in a CLI-supplied parameter, asserting Maven 3 semantics viamaven-it-plugin-configuration); it fails on current master withexpected: <PRE-…-POST> but was: <PRE--POST>. PR follows.cc @gnodet — given the launcher fix lands in rc-6, this becomes the first user-visible failure mode for the
${...}-in--Dpattern there.