Understanding Maven's Internal Architecture: Core Execution Model and Runtime Objects

Apache Maven's internal architecture implements a model → build → execution pipeline through five core runtime objects—MavenExecutionRequest, MavenSession, MavenProject, MavenExecutionResult, and ReactorGraph—that orchestrate CLI configuration, project resolution, lifecycle execution, and dependency management.

The Apache Maven source code (apache/maven) reveals a tightly-coupled modular design where immutable data structures and thread-local contexts collaborate to transform user input into completed builds. Understanding Maven's internal architecture requires tracing how these central objects interact from bootstrap to result aggregation, managing everything from POM inheritance to plugin isolation.

Core Runtime Objects

Maven's execution layer revolves around five primary concepts that hold state during the build lifecycle.

MavenExecutionRequest: The Configuration Holder

The MavenExecutionRequest class serves as a mutable data aggregator that captures all command-line options, system properties, profiles, and repository settings before execution begins. Defined in impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java, this object translates CLI arguments into structured fields including goals, offline mode flags, and base directory paths.

MavenSession: The Build Context

MavenSession acts as the central runtime object that persists for the entire build duration. Stored in impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java, it holds the MavenExecutionRequest, the RepositorySystemSession (Aether resolver), the ordered list of reactor projects, and thread-local state for the current project. The session maintains two critical project lists: the sorted projects for execution order and allProjects for aggregation operations.

MavenProject: The Runtime POM Representation

After model inheritance and interpolation, MavenProject provides the runtime view of a single POM. Located in impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java, this class exposes resolved artifacts, source roots (SourceRoot), dependencies, and plugin descriptors. It preserves the original model (originalModel) while adding runtime-only data such as resolved classpath elements.

MavenExecutionResult: The Build Outcome

MavenExecutionResult produces an immutable snapshot of the completed build. Implemented in impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java, it encapsulates the final MavenSession, accumulated exceptions, and the list of collected projects returned to the CLI layer.

ReactorGraph: Module Visualization

The ReactorGraph utility generates DOT/SVG representations of module dependencies. Found in src/graph/ReactorGraph.java, this visualization tool clusters artifacts by logical groups—such as Maven API, implementation, and resolver components—helping developers understand the dependency landscape of multi-module builds.

The Build Execution Pipeline

Maven's architecture follows a strict seven-phase pipeline that transforms user input into build results.

  1. Bootstrapping – The CLI (org.apache.maven.Maven) instantiates a MavenExecutionRequest and populates it with command-line arguments, system properties, and profile activations.

  2. Session creation – The request initializes a MavenSession containing the RepositorySystemSession and a thread-local MavenProject pointing to the execution root.

  3. Project building – The ProjectBuilder reads each pom.xml, resolves parent POMs, applies profile activation (ProjectActivation/ProfileActivation), and produces fully populated MavenProject instances with resolved Artifact objects.

  4. Reactor construction – Maven sorts projects topologically using ProjectDependencyGraph and stores the ordered list in MavenSession.projects, maintaining the full list in allProjects for aggregation.

  5. Lifecycle execution – The DefaultLifecycleExecutor walks lifecycle phases, resolves classpaths (using getCompileClasspathElements or the new JavaPathType API), and invokes plugins while storing contexts in MavenSession.pluginContextsByProjectAndPluginKey.

  6. Dependency resolution – Aether (org.eclipse.aether) utilizes the RepositorySystemSession to download artifacts, apply checksum policies, and populate MavenProject.artifacts and resolvedArtifacts.

  7. Result aggregationMavenExecutionResult captures exceptions, the final session, and collected projects, returning this immutable structure to the CLI for success/failure reporting.

Key Architectural Patterns

Several design patterns ensure thread safety and backward compatibility within Maven's architecture.

Thread-Local Project Isolation

MavenSession uses a ThreadLocal<MavenProject> (currentProject) to maintain the "current" project without explicit parameter passing. This allows plugins to safely query context during parallel multi-module builds without cross-contamination.

Plugin Context Isolation

The pluginContextsByProjectAndPluginKey map guarantees that plugin state remains isolated per project. This prevents state leakage between modules when the same plugin executes across different projects in the reactor.

Immutable Model vs. Mutable Runtime

Maven maintains strict separation between the immutable originalModel (the parsed POM) and the mutable MavenProject runtime view. The runtime object adds resolved dependencies and source roots while preserving the original XML structure for reference.

Compatibility Layers

Maven 4 introduces new APIs like SourceRoot while preserving deprecated methods such as getCompileClasspathElements. These legacy methods delegate to newer implementations, allowing existing plugins to function while migrating to immutable designs.

Reactor Graph Visualization

The ReactorGraph tool parses DOT files from the graph plugin, applies clustering heuristics based on coordinate patterns (e.g., org.apache.maven.api vs. org.apache.maven.impl), and emits SVGs that reveal module coupling and API boundaries.

Working with Maven's API

The following examples demonstrate programmatic interaction with Maven's core execution objects.

Creating a Maven Execution Request

import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import java.util.Collections;

MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setGoals(Collections.singletonList("install"));
request.addUserProperty("skipTests", "true");
request.setOffline(false);
request.setBaseDirectory(new File("/path/to/project"));

Source: MavenExecutionRequest setters in impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java (lines 19-31).

Building a Session from a Request

import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.MavenExecutionResult;
import org.eclipse.aether.RepositorySystemSession;

// Assume a populated request and RepositorySystemSession `repoSession`
MavenExecutionResult result = new MavenExecutionResult();
MavenSession session = new MavenSession(repoSession, request, result);

// Access ordered projects
List<MavenProject> sorted = session.getProjects();
MavenProject current = session.getCurrentProject();

Source: MavenSession constructor and accessors in impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java (lines 103-112, 141-149).

Adding Source Roots (Maven 4 API)

import org.apache.maven.project.MavenProject;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.Language;
import java.nio.file.Path;

MavenProject project = ...;               // obtained from session
Path src = Path.of("src/main/java");

// Adds source only if it does not exist
project.addSourceRoot(ProjectScope.MAIN, Language.JAVA_FAMILY, src);

Source: MavenProject.addSourceRoot(ProjectScope, Language, Path) in impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java (lines 447-452).

Retrieving Compile Classpath (Legacy API)

Set<Artifact> compileArtifacts = project.getArtifacts()
    .stream()
    .filter(a -> MavenProject.isCompilePathElement(a.getScope()))
    .collect(Collectors.toSet());

List<String> classpath = project.getCompileClasspathElements(); // deprecated

Source: MavenProject.getCompileClasspathElements in impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java (lines 560-566).

Visualizing the Reactor Graph


# Inside the Maven source tree

jbang src/graph/ReactorGraph.java

# Generates target/graph/high_level_graph.svg with clusters like "Maven API", "Sisu", etc.

Source: ReactorGraph.main in src/graph/ReactorGraph.java (lines 57-70).

Summary

  • MavenExecutionRequest acts as the mutable configuration holder that bridges CLI arguments to the internal execution model.
  • MavenSession provides the thread-safe runtime context, using ThreadLocal to isolate the current project during multi-module builds.
  • MavenProject represents the mutable runtime view of a POM, preserving the immutable originalModel while adding resolved artifacts and source roots.
  • ReactorGraph offers visualization capabilities for understanding module dependencies through clustered SVG generation.
  • The architecture maintains backward compatibility through delegation patterns, allowing Maven 4's immutable SourceRoot API to coexist with deprecated classpath methods.

Frequently Asked Questions

What is the difference between MavenExecutionRequest and MavenSession?

MavenExecutionRequest is a mutable data object that holds configuration before the build starts, while MavenSession is the persistent runtime context that lives throughout the build. The session contains the request, repository system session, and project state, whereas the request simply captures initial settings from the CLI and settings.xml.

How does Maven isolate plugin state in multi-module builds?

Maven uses a pluginContextsByProjectAndPluginKey map within MavenSession to ensure plugin contexts remain isolated per project. This prevents state leakage when the same plugin executes across different modules in the reactor, maintaining thread safety during parallel builds.

Why does Maven use ThreadLocal for the current project?

MavenSession stores the current project in a ThreadLocal<MavenProject> variable named currentProject to allow plugins to access build context without explicit parameter passing. This design enables safe concurrent execution while maintaining backward compatibility with plugins that expect to retrieve the project from the session implicitly.

How has Maven 4 changed the source root API?

Maven 4 introduces the immutable SourceRoot API and deprecates methods like getCompileClasspathElements in favor of type-safe classpath access via JavaPathType. The addSourceRoot(ProjectScope, Language, Path) method now provides explicit scope and language binding, replacing the legacy string-based source root management while maintaining delegation compatibility for existing plugins.

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 →