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 MavenExecutionRequest and MavenExecutionResult
  • The current MavenProject and 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:

  • afterSessionStart
  • afterProjectsRead
  • afterSessionEnd

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:

  1. Validate Local Repository - validateLocalRepository(request) (lines 376-387) ensures the local repository exists and is writable.

  2. Create MavenSession - newCloseableSession(request, chainedWorkspaceReader) instantiates the session (lines 162-176), capturing all build state including the Aether repository session.

  3. Enter Session Scope - sessionScope.enter() (lines 112-119) makes @SessionScoped components injectable into lifecycle participants.

  4. Fire afterSessionStart - callListeners(..., AbstractMavenLifecycleParticipant::afterSessionStart) (lines 274-282) notifies extensions that the session is ready.

  5. Add Reactor Workspace Reader - lookup(WorkspaceReader.class, ReactorReader.HINT) (line 243) enables resolution of projects within the current reactor.

  6. Discover Projects - eventCatapult.fire(ProjectDiscoveryStarted, ...) and graphBuilder.build(session) (lines 248-254) resolve all POMs and build the initial ProjectDependencyGraph.

  7. Set Project Map and Workspace Readers - setProjectMap(...) (line 260) and setupWorkspaceReader(...) (lines 306-312) configure the session for the resolved projects.

  8. Fire afterProjectsRead - Extensions can manipulate the model before the graph is re-sorted.

  9. Rebuild Graph - A second call to graphBuilder.build(session) (lines 322-330) incorporates any changes made by participants.

  10. Validate Prerequisites and Profiles - Methods validatePrerequisitesForNonMavenPluginProjects, validateRequiredProfiles, and validateOptionalProfiles (lines 360-410) ensure build requirements are met.

  11. Start Lifecycle - lookupOptional(LifecycleStarter.class, ...).execute(session) (lines 388-393) begins the actual build execution.

  12. Persist Resumption Data - If an exception occurs, persistResumptionData(result, session) (lines 417-426) saves state for build resumption.

  13. Fire afterSessionEnd - Cleanup hook (lines 438-442) notifies extensions that the build is complete.

  14. Return Result - The MavenExecutionResult contains 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:

  • DefaultMaven orchestrates the entire build lifecycle from validation to completion
  • MavenSession maintains mutable state including the project list and dependency graph
  • GraphBuilder constructs the topologically sorted reactor graph before execution
  • LifecycleStarter drives the actual plugin execution phase
  • AbstractMavenLifecycleParticipant provides extension points for customizing the build process
  • MavenChainedWorkspaceReader enables 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:

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 →