# Maven Execution Context for Custom Goals: How Apache Maven Builds and Runs Your Mojo

> Explore the Maven execution context for custom goals. Learn how Maven builds and runs your Mojo using MojoExecution, MavenSession, and MojoExecutionScope for isolation.

- Repository: [The Apache Software Foundation/maven](https://github.com/apache/maven)
- Tags: internals
- Published: 2026-07-06

---

**Maven constructs an execution context for every custom goal by combining a `MojoExecution` object, a `MavenSession` for global state, and a `MojoExecutionScope` for dependency injection, then runs the Mojo's `execute()` method within this isolated scope.**

When you create a custom goal in Apache Maven, the core engine assembles a sophisticated execution context that manages everything from plugin resolution to component injection. Understanding how Maven builds this context—centered around `MojoExecution`, `MavenSession`, and `MojoExecutionScope`—is essential for writing robust plugins and debugging lifecycle issues. This article breaks down the exact source code paths and mechanisms that power the Maven execution context for custom goals.

## The Three Pillars of the Execution Context

Every custom goal invocation relies on three interconnected components that define the *what*, *where*, and *how* of the build.

### MojoExecution: The Goal Descriptor

The **MojoExecution** class ([`impl/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java)) encapsulates the specific configuration for a single goal execution. It stores the `MojoDescriptor` (metadata about the goal), the execution ID, the source (CLI, POM, or lifecycle), and the raw XML configuration (`Xpp3Dom` or `XmlNode`). When Maven prepares to run your custom goal, it instantiates this object using constructors defined at lines 105–108, which accept a `MojoDescriptor` and the configuration node.

### MavenSession: The Build State

The **MavenSession** ([`impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java)) provides the global execution context. It holds the current project, the full reactor graph, user properties, and the repository session. Custom goals access this session to read cross-project state or write to the shared `pluginContext` map.

### MojoExecutionScope: The DI Container

The **MojoExecutionScope** ([`impl/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java)) is a Guice `Scope` that creates a fresh dependency-injection container for each goal execution. This scope caches components for the duration of the goal and propagates lifecycle events to `WeakMojoExecutionListener` instances, ensuring that plugins can observe execution boundaries without causing classloader leaks.

## Step-by-Step Execution Flow

When you invoke a custom goal, Maven executes a precise sequence of resolution, construction, and invocation steps.

### Goal Resolution and Descriptor Loading

Maven first reads the plugin's [`plugin.xml`](https://github.com/apache/maven/blob/main/plugin.xml) (generated by the `maven-plugin-plugin`) to create a `MojoDescriptor`. The `DefaultMavenPluginManager#getMojoDescriptor(..)` method at line 290 of [`impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java) handles this lookup, returning a descriptor that specifies the implementation class, required parameters, and thread-safety flags.

### Creating the MojoExecution Object

With the descriptor resolved, Maven constructs a `MojoExecution` object that wraps the descriptor, the goal name, and any user-supplied configuration. This object is created via the `MojoExecution` constructor that takes a `MojoDescriptor` and an `XmlNode`, binding the static plugin metadata to the dynamic runtime configuration.

### Building the Execution Plan

The `MojoExecution` is inserted into a `MavenExecutionPlan` ([`impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java)). The `DefaultLifecycleExecutionPlanCalculator` generates this plan by calling `MojoDescriptorCreator#getMojoDescriptor(..)` at line 136 of [`impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoDescriptorCreator.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoDescriptorCreator.java), resolving descriptors for each task in the build lifecycle.

### Activating the Mojo Execution Scope

Before the goal runs, Maven opens a **Mojo-execution scope** via `MojoExecutionScope`. This scope initializes a per-execution cache and fires *before* callbacks to registered `WeakMojoExecutionListener` instances. The scope ensures that components annotated with `@MojoExecutionScoped` are instantiated fresh for each goal and disposed of afterward.

### Component Injection and Goal Execution

Inside the active scope, Maven uses `PlexusContainer` (or the newer `org.apache.maven.di`) to instantiate the concrete Mojo class. Fields annotated with `@Parameter` are populated from the `MojoExecution` configuration using the `PluginParameterExpressionEvaluator` ([`impl/maven-core/src/main/java/org/apache/maven/plugin/PluginParameterExpressionEvaluator.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/PluginParameterExpressionEvaluator.java)) to resolve `${...}` expressions. Finally, Maven invokes the Mojo's `execute()` method and records the outcome in `MavenExecutionResult`.

## Key Architectural Concepts

Understanding these core abstractions helps you navigate the Maven source code and extend plugin functionality.

- **MojoDescriptor** ([`compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java`](https://github.com/apache/maven/blob/main/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java)): Describes a goal's implementation class, required parameters, and thread-safety characteristics.
- **PluginDescriptor** ([`compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptor.java`](https://github.com/apache/maven/blob/main/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptor.java)): Holds the collection of `MojoDescriptor`s for a plugin and the plugin's artifact coordinates.
- **MojoExecutionScope** ([`impl/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java)): Guarantees a fresh DI container per goal execution and manages listener callbacks via `WeakMojoExecutionListener`.
- **MavenExecutionPlan** ([`impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java)): An ordered list of `MojoExecution`s that Maven iterates through during the build.

## Practical Code Examples

### Minimal Custom Maven Plugin

Create a plugin with a single goal that prints a configurable message:

```xml
<!-- pom.xml of the plugin -->
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0.0</version>
  <packaging>maven-plugin</packaging>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>3.12.0</version>
        <configuration>
          <goalPrefix>hello</goalPrefix>
        </configuration>
        <executions>
          <execution>
            <goals><goal>helpmojo</goal></goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

```

```java
// src/main/java/com/example/HelloMojo.java
package com.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.*;

@Mojo(name = "sayhi", defaultPhase = LifecyclePhase.NONE, requiresProject = false)
public class HelloMojo extends AbstractMojo {

    /** Message printed by the mojo – configurable from the command line */
    @Parameter(property = "message", defaultValue = "Hello, Maven!")
    private String message;

    public void execute() throws MojoExecutionException {
        getLog().info(message);
    }
}

```

When you run the goal from the command line:

```bash
mvn com.example:hello-maven-plugin:1.0.0:sayhi -Dmessage="Hi from a custom goal"

```

Maven internally creates the execution context:

```java
MojoExecution exec = new MojoExecution(
        mojoDescriptor,               // resolved from plugin.xml
        "sayhi",                     // goal name
        "default",                   // executionId
        MojoExecution.Source.CLI);   // source = CLI

```

This `exec` object is placed into the `MavenExecutionPlan` and executed inside a fresh `MojoExecutionScope`.

### Accessing the Execution Context from Inside a Mojo

Inject session and project parameters to interact with the build state:

```java
@Mojo(name = "inspect")
public class InspectMojo extends AbstractMojo {

    @Parameter(defaultValue = "${session}", readonly = true)
    private MavenSession session;          // injected session

    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject project;          // current project

    public void execute() {
        getLog().info("Current project: " + project.getArtifactId());
        getLog().info("User properties: " + session.getUserProperties());
        getLog().info("All goals for this build: " + session.getGoals());
    }
}

```

Because `MojoExecutionScope` caches the session and project objects, every custom goal sees a **consistent view** of the reactor. You can safely read from or write to the `pluginContext` map to share state between goals:

```java
Map<String, Object> ctx = session.getPluginContext(pluginDescriptor, project);
ctx.put("myKey", "myValue");   // later goals in the same session can read this value

```

## Summary

- **Maven execution context** for custom goals consists of three components: `MojoExecution` (the configuration), `MavenSession` (the build state), and `MojoExecutionScope` (the DI container).
- **Resolution** happens via `DefaultMavenPluginManager#getMojoDescriptor` in [`impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java).
- **Construction** involves creating a `MojoExecution` object and adding it to the `MavenExecutionPlan` managed by `DefaultLifecycleExecutionPlanCalculator`.
- **Isolation** is guaranteed by `MojoExecutionScope`, which provides per-execution component caching and listener callbacks.
- **Injection** of `@Parameter` fields uses `PluginParameterExpressionEvaluator` to resolve expressions before `execute()` is called.

## Frequently Asked Questions

### How does Maven resolve the correct Mojo class for a custom goal?

Maven resolves the Mojo class by reading the [`plugin.xml`](https://github.com/apache/maven/blob/main/plugin.xml) descriptor bundled in the plugin JAR. The `DefaultMavenPluginManager#getMojoDescriptor(..)` method at line 290 of [`impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java) loads this descriptor and returns a `MojoDescriptor` containing the fully qualified implementation class name.

### What is the difference between MavenSession and MojoExecution?

`MavenSession` ([`impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java)) provides global build state including the reactor projects and user properties, while `MojoExecution` ([`impl/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java)) represents the specific configuration and metadata for a single goal invocation. The session persists across the entire build, whereas a new `MojoExecution` is created for each goal.

### Can custom goals share state between executions?

Yes, custom goals can share state using the `pluginContext` map accessible via `session.getPluginContext(pluginDescriptor, project)`. Additionally, `MojoExecutionScope` ensures that components scoped to the execution are cached for the duration of the goal, while `WeakMojoExecutionListener` allows plugins to observe execution boundaries without retaining strong references that could cause memory leaks.

### Where does Maven handle expression evaluation for @Parameter values?

Maven evaluates expressions like `${project.version}` or `${session}` using the `PluginParameterExpressionEvaluator` class located at [`impl/maven-core/src/main/java/org/apache/maven/plugin/PluginParameterExpressionEvaluator.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/plugin/PluginParameterExpressionEvaluator.java). This evaluation occurs during the component injection phase, immediately before the Mojo's `execute()` method is invoked.