Understanding the Maven Execution Model: Lifecycles, Phases, and Execution Plans
The Maven execution model is a deterministic system that translates command-line goals into an immutable sequence of plugin executions (Mojos) by mapping lifecycles and phases through the LifecycleExecutor to create a MavenExecutionPlan.
The Maven execution model forms the architectural backbone of Apache Maven, determining how build commands transform into concrete tool executions. In the apache/maven repository, this model is implemented primarily within the org.apache.maven.lifecycle package, where the core runtime orchestrates everything from lifecycle mapping to the final execution of build steps.
Core Concepts of the Maven Execution Model
At its foundation, the model organizes build operations into three hierarchical concepts: lifecycles, phases, and goals (Mojos).
Lifecycles and Phases
Maven defines a set of default lifecycles—specifically default, clean, and site—each representing an ordered list of phases such as validate, compile, test, and package. The mapping from phase names to their respective lifecycles, along with the complete ordered phase list for each lifecycle, is maintained in DefaultLifecycles. This class, located at impl/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycles.java, serves as the definitive registry for built-in build sequences.
Goals and Mojos
While phases define the build sequence, goals represent the actual work performed. Each goal corresponds to a Mojo (Maven Plain Old Java Object), which is a specific task implemented by a plugin. The execution model binds these Mojos to phases, creating a concrete list of operations to perform.
Building the Execution Plan
When you invoke a command like mvn clean install, Maven does not immediately execute plugins. Instead, it constructs an immutable MavenExecutionPlan through a multi-stage calculation process.
Task Segmentation and Resolution
The entry point is the DefaultLifecycleExecutor (impl/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java), which implements the LifecycleExecutor interface. This component first uses a LifecycleTaskSegmentCalculator to divide the requested command-line tasks into discrete segments. It then invokes the LifecyclePluginResolver to resolve any missing plugin versions before the plan is calculated, ensuring all required build tools are identified and versioned.
Calculating Mojo Executions
The core calculation logic resides in DefaultLifecycleExecutionPlanCalculator at impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java. This component performs three critical operations:
calculateMojoExecutions– Generates the ordered list ofMojoExecutionobjects required for the requested tasks.setupMojoExecutions(optional) – Enriches eachMojoExecutionwith its plugin descriptor and configuration, resolving any late-bound parameters.- Plan Assembly – Wraps each
MojoExecutionin an immutableExecutionPlanItemand assembles the finalMavenExecutionPlan.
The Immutable Execution Plan
The MavenExecutionPlan class (impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java) represents the definitive, immutable blueprint for the build. Once constructed, this plan cannot be modified, guaranteeing reproducibility across build runs.
The plan maintains several key data structures:
phasesInExecutionPlan– A pre-computed list of distinct phases in execution order.lastMojoExecutionForAllPhases– A mapping that allows Maven to quickly locate the final Mojo for any given phase, essential for incremental builds and reporting.- ExecutionPlanItem list – An ordered collection where each item wraps a single
MojoExecutionalong with its execution context.
Utility methods expose the list of MojoExecutions, identify non-thread-safe plugins, and provide phase-specific lookups.
Execution Flow
After calculation, control flows back to DefaultLifecycleExecutor, which delegates to the LifecycleStarter to execute the immutable plan. The high-level flow follows this sequence:
- The Maven CLI invokes
DefaultLifecycleExecutor.calculateExecutionPlan(). - Task segments are calculated and plugin versions are resolved.
DefaultLifecycleExecutionPlanCalculatorbuilds theMavenExecutionPlancontainingExecutionPlanItems.LifecycleStarter.execute(session)runs the plan against the current Maven session.
This separation between plan calculation and plan execution ensures that the build logic is validated and locked before any file system modifications occur.
Programmatically Accessing the Execution Model
You can interact with the Maven execution model programmatically using the LifecycleExecutor interface. Below are practical examples for inspecting build plans.
Printing the Execution Plan
This example demonstrates how to obtain and display the execution plan for a given set of tasks:
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.lifecycle.MavenExecutionPlan;
public class PlanPrinter {
private final LifecycleExecutor executor;
public PlanPrinter(LifecycleExecutor executor) {
this.executor = executor;
}
public void printPlan(MavenSession session, String... tasks) throws Exception {
MavenExecutionPlan plan = executor.calculateExecutionPlan(session, tasks);
System.out.println("Execution plan for tasks: " + String.join(" ", tasks));
plan.getMojoExecutions().forEach(m ->
System.out.println("- " + m.getPlugin().getArtifactId() + ":" + m.getGoal() + " (" + m.getLifecyclePhase() + ")")
);
}
}
Finding the Last Mojo in a Phase
You can leverage the plan's phase mapping to identify specific execution points:
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.lifecycle.internal.ExecutionPlanItem;
MavenExecutionPlan plan = executor.calculateExecutionPlan(session, "install");
ExecutionPlanItem lastCompile = plan.findLastInPhase("compile");
if (lastCompile != null) {
System.out.println("Last compile mojo: " + lastCompile.getMojoExecution().getGoal());
}
Summary
- The Maven execution model organizes builds into lifecycles, phases, and goals, with definitions stored in
DefaultLifecycles. DefaultLifecycleExecutionPlanCalculatortransforms command-line requests into an immutableMavenExecutionPlanby resolving plugins and calculatingMojoExecutions.- The execution plan pre-computes phase ordering and mojo mappings, storing them in
ExecutionPlanItemwrappers for efficient lookup. DefaultLifecycleExecutorserves as the façade, delegating plan construction to the calculator and execution toLifecycleStarter.- The plan's immutability ensures build reproducibility once the calculation phase completes.
Frequently Asked Questions
What is the difference between a Maven phase and a goal?
A phase represents a stage in the build lifecycle (such as compile or test), defined as an ordered list within DefaultLifecycles. A goal represents a specific task performed by a plugin (a Mojo). When you run a phase, Maven executes all goals bound to that phase and all preceding phases in the lifecycle. Goals can also be invoked directly by name.
How does Maven resolve plugin versions when building the execution plan?
During the plan calculation phase, DefaultLifecycleExecutionPlanCalculator invokes LifecyclePluginResolver to resolve any plugin versions not explicitly declared in the POM. This resolution occurs before calculateMojoExecutions is called, ensuring that every MojoExecution in the final plan has a concrete, resolved plugin version.
Is the Maven execution plan mutable during the build?
No. The MavenExecutionPlan is immutable once constructed by the LifecycleExecutionPlanCalculator. It contains an ordered list of ExecutionPlanItem objects that cannot be modified after creation. This design guarantees that the build steps remain deterministic and reproducible throughout the execution phase managed by LifecycleStarter.
What component actually runs the execution plan?
While DefaultLifecycleExecutionPlanCalculator builds the plan, the DefaultLifecycleExecutor delegates the actual execution to LifecycleStarter. The executor calculates the plan first, then passes the immutable MavenExecutionPlan to the starter, which iterates through the ExecutionPlanItems and invokes each Mojo against the current MavenSession.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →