apache/maven · error · ExpressionEvaluationException

Error evaluating plugin parameter expression

Error message

Error evaluating plugin parameter expression: ${expression}

What it means

The Maven 4 evaluator (PluginParameterExpressionEvaluatorV4) resolves the expression against the consumer-POM model context (ctx.getValue(), typically the org.apache.maven.api Project or Session) and that reflective evaluation threw. It is the same ExpressionEvaluationException family, but rooted in the new consumer-API objects whose getter surface differs from the legacy MavenProject/Settings.

Solutions

  1. Check the path against the org.apache.maven.api model interfaces (Project, Session, Settings) rather than the legacy classes.
  2. Move custom data you need in expressions into user properties (-D) or <properties>, which survive consumer-POM processing.
  3. Test with mvn help:evaluate -DforceStdout under the same Maven major version you build with.
  4. Avoid deep chains; reference only first- or second-level getters.

Example fix

<!-- before: legacy-only path, absent on the Maven 4 consumer Project -->
<value>${project.model.build}</value>
<!-- after: use the consumer-model surface or a property -->
<value>${project.build.directory}</value>
Defensive patterns

Strategy: validation

Validate before calling

mvn -q org.apache.maven.plugins:maven-help-plugin:3.4.1:evaluate \
  -Dexpression=project.build.directory -DforceStdout || echo 'BAD EXPRESSION'

Try / catch

try {
    v = evaluatorV4.evaluate(expr, String.class);
} catch (ExpressionEvaluationException e) {
    // fall back to a plain user property so builds stay reproducible across model versions
    v = session.getUserProperties().getProperty(expr.replaceAll("[^a-zA-Z0-9.]", ""));
}

Prevention

When it happens

Trigger: An expression that resolves on the legacy model but points at a property missing on the v4 consumer model; a nested path traversing a null intermediate on the v4 object graph; expressions referencing build-time-only data stripped from the consumer POM.

Common situations: Plugins migrated to the Maven 4 API; builds where the consumer POM no longer carries elements (distribution, development ids) that the expression expects; mixed 3.x/4.x expression behavior.

Related errors


AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21). Data as JSON: /api/errors/ab2b24596cea5d26. Report an issue: GitHub.

Appendix: source

Thrown at impl/maven-core/src/main/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4.java:161

                try {
                    int pathSeparator = expression.indexOf('/');
                    if (pathSeparator > 0) {
                        String pathExpression = expression.substring(0, pathSeparator);
                        value = ReflectionValueExtractor.evaluate(pathExpression, ctx.getValue());
                        if (pathSeparator < expression.length() - 1) {
                            if (value instanceof Path path) {
                                value = path.resolve(expression.substring(pathSeparator + 1));
                            } else {
                                value = value + expression.substring(pathSeparator);
                            }
                        }
                    } else {
                        value = ReflectionValueExtractor.evaluate(expression, ctx.getValue());
                    }
                    break;
                } catch (Exception e) {
                    // TODO don't catch exception
                    throw new ExpressionEvaluationException(
                            "Error evaluating plugin parameter expression: " + expression, e);
                }
            }
        }

        /*
         * MNG-4312: We neither have reserved all of the above magic expressions nor is their set fixed/well-known (it
         * gets occasionally extended by newer Maven versions). This imposes the risk for existing plugins to
         * unintentionally use such a magic expression for an ordinary property. So here we check whether we
         * ended up with a magic value that is not compatible with the type of the configured mojo parameter (a string
         * could still be converted by the configurator so we leave those alone). If so, back off to evaluating the
         * expression from properties only.
         */
        if (value != null && type != null && !(value instanceof String) && !isTypeCompatible(type, value)) {
            value = null;
        }

        if (value == null) {

View on GitHub (pinned to e4093d4e12)