How to Understand Maven's Core Implementation: A Complete Guide to the Build Lifecycle
Maven's core implementation orchestrates builds through the DefaultMaven class, which validates the environment, creates a MavenSession, builds a project dependency graph via GraphBuilder, and executes the lifecycle through LifecycleStarter, with extension points provided by AbstractMavenLifecycleParticipant.
Apache Maven's core is a sophisticated build orchestration engine that transforms POM files into executable build steps. To truly understand Maven's core implementation, you need to examine the interplay between session management, graph construction, and lifecycle execution as implemented in the apache/maven repository. This guide walks through the actual source code, showing how DefaultMaven coordinates the build process from CLI invocation to plugin execution.
Core Architecture Components
Maven's architecture is built around a small set of high-level services that drive the build lifecycle. These components work together to transform execution requests into completed builds.
The Maven Interface and DefaultMaven
The Maven interface in impl/maven-core/src/main/java/org/apache/maven/Maven.java declares the single entry point execute(MavenExecutionRequest) that all clients call. The concrete implementation, DefaultMaven (impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java), orchestrates the entire build process:
- Validates the local repository
- Creates a
RepositorySystemSession - Builds a
MavenSession - Runs lifecycle participants
- Constructs the project graph
- Starts the lifecycle execution
MavenSession and Execution State
MavenSession (impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java) holds the mutable state for a single build. It contains:
- The
MavenExecutionRequestandMavenExecutionResult - The current
MavenProjectand full project lists - The
ProjectDependencyGraph - Plugin contexts
- The underlying Aether
RepositorySystemSession
MavenExecutionRequest (impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java) carries all CLI options, goals, profiles, and properties, while MavenExecutionResult (impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java) collects the produced projects and any exceptions.
GraphBuilder and ProjectDependencyGraph
The GraphBuilder (impl/maven-core/src/main/java/org/apache/maven/graph/GraphBuilder.java) resolves all POM relationships and produces a ProjectDependencyGraph that is topologically sorted. This determines the reactor build order.
LifecycleStarter and Plugin Execution
LifecycleStarter (impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java) runs the build after the graph is ready. It iterates over each project, creates MojoExecutions for the requested phases/goals, and delegates to the BuildPluginManager (impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java).
The MavenPluginManager (impl/maven-core/src/main/java/org/apache/maven/plugin/MavenPluginManager.java) resolves plugin descriptors and creates plugin class realms.
Extension Points with AbstractMavenLifecycleParticipant
AbstractMavenLifecycleParticipant (impl/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java) provides hooks for core extensions at three points:
afterSessionStartafterProjectsReadafterSessionEnd
Workspace Resolution
MavenChainedWorkspaceReader (impl/maven-core/src/main/java/org/apache/maven/resolver/MavenChainedWorkspaceReader.java) chains the reactor reader, IDE reader, and any custom readers so that artifacts can be resolved from the current reactor, the IDE's workspace, or the local repository.
Execution Flow in DefaultMaven
The entry point DefaultMaven.execute(request) (lines 44-65 in impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java) delegates to doExecute(request), which orchestrates the build through these distinct phases:
-
Validate Local Repository -
validateLocalRepository(request)(lines 376-387) ensures the local repository exists and is writable. -
Create MavenSession -
newCloseableSession(request, chainedWorkspaceReader)instantiates the session (lines 162-176), capturing all build state including the Aether repository session. -
Enter Session Scope -
sessionScope.enter()(lines 112-119) makes@SessionScopedcomponents injectable into lifecycle participants. -
Fire afterSessionStart -
callListeners(..., AbstractMavenLifecycleParticipant::afterSessionStart)(lines 274-282) notifies extensions that the session is ready. -
Add Reactor Workspace Reader -
lookup(WorkspaceReader.class, ReactorReader.HINT)(line 243) enables resolution of projects within the current reactor. -
Discover Projects -
eventCatapult.fire(ProjectDiscoveryStarted, ...)andgraphBuilder.build(session)(lines 248-254) resolve all POMs and build the initialProjectDependencyGraph. -
Set Project Map and Workspace Readers -
setProjectMap(...)(line 260) andsetupWorkspaceReader(...)(lines 306-312) configure the session for the resolved projects. -
Fire afterProjectsRead - Extensions can manipulate the model before the graph is re-sorted.
-
Rebuild Graph - A second call to
graphBuilder.build(session)(lines 322-330) incorporates any changes made by participants. -
Validate Prerequisites and Profiles - Methods
validatePrerequisitesForNonMavenPluginProjects,validateRequiredProfiles, andvalidateOptionalProfiles(lines 360-410) ensure build requirements are met. -
Start Lifecycle -
lookupOptional(LifecycleStarter.class, ...).execute(session)(lines 388-393) begins the actual build execution. -
Persist Resumption Data - If an exception occurs,
persistResumptionData(result, session)(lines 417-426) saves state for build resumption. -
Fire afterSessionEnd - Cleanup hook (lines 438-442) notifies extensions that the build is complete.
-
Return Result - The
MavenExecutionResultcontains the final list of projects, any exceptions, and the resume flag.
Practical Code Examples
Programmatically Executing a Maven Build
You can run Maven programmatically using the core API, mirroring the steps performed inside DefaultMaven.doExecute:
import org.apache.maven.*;
import org.apache.maven.execution.*;
import org.apache.maven.api.services.*;
import org.apache.maven.internal.impl.DefaultSessionFactory;
import org.apache.maven.di.*;
public class MavenInvoker {
public static void main(String[] args) throws Exception {
// Build a simple execution request
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setBaseDirectory(new File("."));
request.setGoals(List.of("clean", "install"));
request.setUserProperties(System.getProperties());
request.setSystemProperties(System.getProperties());
// Obtain the core Maven component (uses Maven's DI container)
Maven maven = new DefaultMaven(
new DefaultLookup(), // Lookup implementation
new ExecutionEventCatapult(), // Event dispatcher
new LegacySupport(), // Legacy support (session holder)
new SessionScope(), // Session scope manager
new RepositorySystemSessionFactory(), // Aether session factory
new GraphBuilderImpl(), // Graph builder implementation
new BuildResumptionAnalyzer(),
new BuildResumptionDataRepository(),
new SuperPomProvider(),
new DefaultSessionFactory(),
null // No IDE WorkspaceReader
);
// Execute the build
MavenExecutionResult result = maven.execute(request);
// Inspect the result
if (result.hasExceptions()) {
result.getExceptions().forEach(Throwable::printStackTrace);
} else {
System.out.println("Build succeeded, projects built:");
result.getTopologicallySortedProjects()
.forEach(p -> System.out.println(" - " + p.getId()));
}
}
}
Key points: This snippet demonstrates how to construct a request, obtain the core Maven implementation (normally wired by Maven's DI container), and run a build programmatically.
Accessing the Reactor Graph
After execution, you can retrieve the ProjectDependencyGraph that the GraphBuilder produced:
MavenExecutionResult result = maven.execute(request);
MavenSession session = ((DefaultMavenExecutionResult) result).getSession(); // cast to internal type
ProjectDependencyGraph graph = session.getProjectDependencyGraph();
System.out.println("Reactor order:");
graph.getSortedProjects().forEach(p -> System.out.println(p.getId()));
This demonstrates how to access the topologically sorted project list from the session.
Creating a Custom Lifecycle Participant
Extend AbstractMavenLifecycleParticipant to intercept the build at extension points:
package com.example;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.execution.MavenSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyParticipant extends AbstractMavenLifecycleParticipant {
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
public void afterProjectsRead(MavenSession session) {
log.info("Projects after reading: {}", session.getProjects().size());
// Example: inject a dummy project property
session.getAllProjects().forEach(p ->
p.getProperties().setProperty("myProp", "hello"));
}
}
Register the participant via a core extension (META-INF/plexus/components.xml). Maven invokes it at the three hook points: afterSessionStart, afterProjectsRead, and afterSessionEnd.
Summary
Understanding Maven's core implementation requires tracing the execution flow through these key components:
DefaultMavenorchestrates the entire build lifecycle from validation to completionMavenSessionmaintains mutable state including the project list and dependency graphGraphBuilderconstructs the topologically sorted reactor graph before executionLifecycleStarterdrives the actual plugin execution phaseAbstractMavenLifecycleParticipantprovides extension points for customizing the build processMavenChainedWorkspaceReaderenables artifact resolution from the reactor, IDE, and local repository
The separation of concerns between session handling, graph construction, extension points, and plugin execution makes Maven's architecture both extensible and maintainable.
Frequently Asked Questions
What is the difference between MavenSession and MavenExecutionRequest?
MavenExecutionRequest is an immutable configuration object that carries all CLI options, goals, profiles, and properties specified by the user. MavenSession is the mutable runtime state that holds the request, the resulting projects, the dependency graph, plugin contexts, and the Aether repository session. While the request defines what to build, the session tracks the actual build progress and outcomes.
How does Maven determine the build order for multi-module projects?
Maven uses the GraphBuilder to construct a ProjectDependencyGraph by analyzing inter-module dependencies declared in the POM files. The graph is topologically sorted so that dependencies are built before the projects that depend on them. This graph is built twice during execution: once initially after project discovery, and again after afterProjectsRead extensions have potentially modified the project list (lines 322-330 in DefaultMaven.java).
What is the purpose of session scope in Maven's core?
Session scope (SessionScope) is a dependency injection mechanism that makes @SessionScoped components available throughout a single build execution. When sessionScope.enter() is called (line 112-119 in DefaultMaven.java), the session becomes injectable into lifecycle participants and other components, ensuring that all parts of the build share the same execution context and state.
How can I customize Maven's behavior without modifying the core?
Extend AbstractMavenLifecycleParticipant and register it as a core extension. This allows you to intercept the build at three specific points: after the session starts (afterSessionStart), after projects are read but before execution (afterProjectsRead), and after the session ends (afterSessionEnd). Common use cases include modifying project properties, injecting custom workspace readers, or implementing CI-friendly versioning schemes.
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 →