How Maven Handles Plugin Parameter Expression Evaluation: Inside the ${...} Resolution Engine

Maven resolves ${...} placeholders in plugin configurations at runtime using the PluginParameterExpressionEvaluator (or Maven 4's PluginParameterExpressionEvaluatorV4), which recursively evaluates magic expressions, navigates object graphs via reflection, and falls back to system and project properties.

Maven plugin parameter expression evaluation is the core mechanism that transforms string placeholders like ${basedir} or ${project.build.directory} into concrete Java objects during Mojo execution. Implemented in the apache/maven repository, this process is handled by the PluginParameterExpressionEvaluator class and its Maven 4 counterpart PluginParameterExpressionEvaluatorV4, both of which implement the TypeAwareExpressionEvaluator interface to provide type-safe value injection.

The Expression Evaluation Architecture

When Maven prepares to execute a Mojo, the DefaultMavenPluginManager instantiates the evaluator and passes it to the component configurator. According to the source code in DefaultMavenPluginManager.java (lines 760-775), the manager creates either the standard evaluator or the V4 variant depending on the Maven version, then hands it off to handle parameter injection. This evaluator acts as the bridge between XML configuration strings and the Java types expected by the plugin.

Step-by-Step Expression Resolution Process

The evaluation follows a strict seven-step pipeline defined in PluginParameterExpressionEvaluator.java:

1. Token Stripping and Normalization

The process begins by removing delimiter characters. If the entire string is wrapped in ${}, the stripTokens method (lines 38-42) removes the opening ${ and closing } characters, leaving the raw expression content for further processing.

2. Recursive Resolution of Nested Expressions

For expressions containing nested ${} fragments, the evaluator applies recursive resolution (lines 36-59). It identifies the innermost placeholder first, evaluates it completely, substitutes the result back into the string, and continues until all placeholders are resolved. This ensures that ${outer.${inner}.property} constructs are handled correctly.

3. Magic Expression Handling

Maven recognizes a fixed set of magic identifiers via an explicit if...else chain (lines 68-78). These provide direct access to core Maven objects:

  • session → Returns the current MavenSession object.
  • project → Returns the current MavenProject instance.
  • basedir → Returns the project base directory, falling back to the execution root or user.dir system property.
  • settings, plugin, and others → Map to their corresponding Maven core objects.

4. Reflection-Based Property Navigation

When an expression starts with a magic identifier followed by a path separator (e.g., project/buildDirectory), Maven uses ReflectionValueExtractor.evaluate (lines 71-90) to walk the object graph. This reflection-based navigation allows access to nested properties like ${project.build.outputDirectory} by traversing the MavenProject object's getter methods and nested objects.

5. Property Fallback Chain

If the expression does not match a magic identifier, Maven queries the property hierarchy in order (lines 93-105):

  1. User-defined properties via session.getUserProperties()
  2. System properties via session.getSystemProperties()
  3. Project properties from the MavenProject

If a property value itself contains ${} placeholders, the evaluator applies recursive evaluation to resolve the nested reference.

6. Type Compatibility Validation

As a TypeAwareExpressionEvaluator, the implementation validates type compatibility when a target type is supplied (lines 119-126). If the resolved value's type is incompatible with the expected parameter type, the evaluator discards the value and falls back to property-based resolution, ensuring that type safety is maintained during injection.

7. File Path Alignment

For File-type parameters, the alignToBaseDirectory method (lines 145-152) converts relative paths to absolute ones. It resolves the file against the project's base directory, ensuring that ${basedir}/target/classes points to the correct absolute path regardless of the working directory.

Practical Examples of Expression Evaluation

Example 1: Basic Directory Resolution in XML

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
        <outputDirectory>${basedir}/target/classes</outputDirectory>
    </configuration>
</plugin>

During execution, the ${basedir} magic expression resolves to the project's base directory through step 3, then alignToBaseDirectory ensures the resulting File object is absolute.

Example 2: Session Injection in a Mojo

@Mojo(name = "print-session")
public class PrintSessionMojo extends AbstractMojo {
    @Parameter(defaultValue = "${session}")
    private MavenSession session;

    public void execute() {
        getLog().info("User property: " + session.getUserProperties().getProperty("myProp"));
    }
}

The ${session} placeholder triggers the magic expression case, injecting the full MavenSession object directly into the Mojo field.

Example 3: Custom Properties with Nested Resolution

<properties>
    <my.custom.dir>${project.basedir}/custom</my.custom.dir>
</properties>

<plugin>
    <artifactId>my-plugin</artifactId>
    <configuration>
        <targetDir>${my.custom.dir}</targetDir>
    </configuration>
</plugin>

Here, my.custom.dir is resolved from project properties (step 5), then the nested ${project.basedir} is recursively evaluated to construct the final path.

Summary

  • Maven plugin parameter expression evaluation converts ${...} strings into Java objects via PluginParameterExpressionEvaluator ( Maven 3.x) or PluginParameterExpressionEvaluatorV4 (Maven 4).
  • Token stripping removes ${} delimiters before processing, while recursive resolution handles nested placeholders.
  • Magic expressions like session, project, and basedir map directly to Maven core objects, with reflection-based navigation supporting property paths like project/buildDirectory.
  • Property fallback checks user, system, and project properties when magic expressions don't match, with recursive evaluation of property values.
  • Type compatibility checks and path alignment ensure values match expected parameter types and file paths are absolute.

Frequently Asked Questions

What is the difference between PluginParameterExpressionEvaluator and PluginParameterExpressionEvaluatorV4?

PluginParameterExpressionEvaluator is the legacy implementation for Maven 3.x, while PluginParameterExpressionEvaluatorV4 is the Maven 4 implementation that uses updated APIs. Both classes implement the same TypeAwareExpressionEvaluator interface and follow identical resolution logic, but the V4 variant aligns with Maven 4's internal API changes.

How does Maven handle nested ${...} expressions in plugin parameters?

Maven evaluates nested expressions recursively, starting with the innermost ${} fragment. The evaluator extracts and resolves the inner placeholder first, substitutes the result back into the containing string, and repeats the process until all placeholders are resolved. This allows constructs like ${project.${property.name}} to work correctly.

What happens if a plugin parameter expression cannot be resolved?

If an expression does not match a magic identifier and is not found in user properties, system properties, or project properties, the evaluator returns the original ${...} string unchanged (or null in some contexts, depending on the type requirement). This typically results in the literal placeholder being passed to the plugin, which may then fail validation or handle the unresolved value according to its own logic.

Can I access arbitrary project properties using expression evaluation?

Yes. You can reference any property defined in the POM's <properties> section, settings.xml, or command line using ${property.name}. Additionally, reflection-based navigation allows access to any readable property on the project object, such as ${project.artifactId}, ${project.build.directory}, or ${project.parent.basedir}.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →