# Understanding Maven's Internal Session Management: Architecture, Thread Safety, and API Usage

> Explore Maven's internal session management architecture. Learn about thread safety, API usage, and how Maven tracks execution state for efficient builds.

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

---

**Maven coordinates build lifecycles through a three-layer session architecture that tracks execution state, manages thread-local project contexts, and provides service lookup to plugins while maintaining immutability through derived sessions.**

Maven's internal session management is the backbone that coordinates multi-module reactor builds, tracks execution outcomes, and supplies repository services to plugins. In the `apache/maven` repository, this system spans from the concrete `MavenSession` implementation in the legacy core to the modern API-first `Session` interface introduced in Maven 4. Understanding these components reveals how Maven achieves thread-safe parallel builds, isolates project state across threads, and enables extensible plugin data sharing.

## The Three-Layer Session Architecture

Maven's session model operates at the intersection of three distinct layers, each serving different compatibility and abstraction needs.

### MavenSession (Legacy Core API)

The `org.apache.maven.execution.MavenSession` class serves as the concrete execution-time representation used by the core and legacy plugin APIs. Located in [`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), this class holds the immutable `MavenExecutionRequest`, `MavenExecutionResult`, and the Eclipse Aether `RepositorySystemSession`. It maintains thread-local state through the `currentProject` field, allowing plugins to query which project is currently being processed by the executing thread.

### Session (Maven 4 API Contract)

Introduced in Maven 4, `org.apache.maven.api.Session` provides an abstraction that shields plugin authors from internal implementation details. Defined in [`api/maven-api-core/src/main/java/org/apache/maven/api/Session.java`](https://github.com/apache/maven/blob/main/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java), this interface exposes operations for project list retrieval, repository handling, artifact resolution, and listener registration. The `DefaultSession` implementation in [`impl/maven-impl/src/main/java/org/apache/maven/internal/impl/DefaultSession.java`](https://github.com/apache/maven/blob/main/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/DefaultSession.java) delegates service lookups to the underlying dependency injection container while providing derived session capabilities.

### Internal Implementation Classes

Beneath the public APIs lie several critical implementation classes that provide the actual thread-safe storage and session lifecycle management:

- **`AbstractSession`** – Base class providing common plumbing for listener management and property handling
- **`DefaultSession`** – Concrete Maven 4 implementation that wraps the repository system and DI container
- **`InternalSession`** – Bridge class connecting legacy `MavenSession` operations with the new API
- **`SessionScope`** – Manages the lifecycle of `@SessionScoped` components, ensuring single-instance-per-build semantics

## Key Session Concepts and State Tracking

The session maintains several distinct state containers that coordinate the build execution.

### Execution Request and Result

The **Maven execution request** (`MavenExecutionRequest`) provides immutable configuration data including goals, properties, and repository settings. Upon build completion, the session populates a **Maven execution result** (`MavenExecutionResult`) containing exceptions and the build success flag.

### Repository System Session

At the Aether level, the `RepositorySystemSession` manages remote and local repository configurations, mirrors, proxies, and authentication. This session is wrapped by the Maven session and exposed to plugins requiring direct repository access.

### Thread-Local Current Project

To support parallel builds, `MavenSession` uses a `ThreadLocal<MavenProject>` field named `currentProject`. When `setProjects(List<MavenProject>)` is called, the first project initializes the thread-local holder. Each thread processing a reactor project clones the session via `MavenSession.clone()` to obtain its own isolated `ThreadLocal` holder, preventing race conditions during multi-threaded execution.

### Plugin Contexts

Plugins store execution state across multiple mojo invocations using **plugin contexts**. Internally, `MavenSession` maintains a `ConcurrentMap` structure keyed by `projectId → pluginKey → userMap`. The `getPluginContext(PluginDescriptor, MavenProject)` method lazily creates these nested maps, guaranteeing a non-null concurrent map for each project/plugin combination.

### Derived Sessions

Sessions support immutability-preserving derivation through methods like `withLocalRepository(LocalRepository)` and `withRemoteRepositories(List<RemoteRepository>)`. These return new `Session` instances that wrap the original but override specific repository configurations, allowing plugins to customize resolution behavior without affecting the global build state.

## Session Lifecycle and Build Coordination

Understanding how Maven constructs and propagates sessions reveals the build coordination strategy.

### Build Startup and Session Construction

1. **Request Creation**: Maven creates a `MavenExecutionRequest` from CLI arguments and [`settings.xml`](https://github.com/apache/maven/blob/main/settings.xml) configuration
2. **Repository Session Initialization**: The core constructs an Eclipse Aether `RepositorySystemSession` with configured mirrors and proxies
3. **MavenSession Instantiation**: The constructor wires together the request, result, repository session, and execution properties (merged system and user properties)

```java
public MavenSession(
        RepositorySystemSession repositorySystemSession,
        MavenExecutionRequest request,
        MavenExecutionResult result) {
    this.request = requireNonNull(request);
    this.result = requireNonNull(result);
    this.repositorySystemSession = requireNonNull(repositorySystemSession);
    Properties executionProperties = new Properties();
    executionProperties.putAll(request.getSystemProperties());
    executionProperties.putAll(request.getUserProperties());
    this.executionProperties = executionProperties;
}

```

### Thread Safety and Project Isolation

When `MavenSession.setProjects()` initializes the reactor, it establishes the thread-local project context:

```java
public void setProjects(List<MavenProject> projects) {
    if (!projects.isEmpty()) {
        MavenProject first = projects.get(0);
        this.currentProject = ThreadLocal.withInitial(() -> first);
        this.topLevelProject = projects.stream()
                .filter(project -> project.isExecutionRoot())
                .findFirst()
                .orElse(first);
    }
    this.projects = projects;
}

```

During parallel builds, each worker thread clones the session to receive its own `ThreadLocal` holder while sharing the immutable request and result data.

### Plugin Context Initialization

When a mojo executes, it retrieves its context map through:

```java
public Map<String, Object> getPluginContext(PluginDescriptor plugin, MavenProject project) {
    String projectKey = project.getId();
    ConcurrentMap<String, ConcurrentMap<String, Object>> pluginContextsByKey =
            pluginContextsByProjectAndPluginKey.computeIfAbsent(projectKey,
                    k -> new ConcurrentHashMap<>());
    String pluginKey = plugin.getPluginLookupKey();
    return pluginContextsByKey.computeIfAbsent(pluginKey,
            k -> new ConcurrentHashMap<>());
}

```

This lazy initialization ensures thread-safe storage for plugin state that must survive across multiple executions of the same plugin within the same build.

## Thread Safety and Session Scope

Maven introduced **session-scoped** components using the `@SessionScoped` annotation to guarantee single-instance-per-build semantics. The `SessionScope` class in [`impl/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java) manages a map keyed by the `Session` instance, ensuring that only one component instance exists for the entire build while allowing safe access from multiple threads.

This differs from project-scoped components (per-project instances) and singletons (JVM-wide instances), providing a middle ground for services that should be shared across the reactor but isolated between separate Maven invocations.

## Practical Code Examples

### Accessing the Current Maven Session in a Mojo

Plugins inject the session using the `${session}` expression, available as both the legacy `MavenSession` and the modern `Session` API:

```java
@Mojo(name = "show-info", defaultPhase = LifecyclePhase.VALIDATE)
public class ShowInfoMojo extends AbstractMojo {

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

    public void execute() throws MojoExecutionException {
        getLog().info("Build started at: " + mavenSession.getStartInstant());
        getLog().info("Top directory: " + mavenSession.getTopDirectory());
        getLog().info("Current project: " + mavenSession.getCurrentProject().getArtifactId());
    }
}

```

### Storing Custom Data in Plugin Contexts

Use the plugin context to persist state across multiple mojo executions:

```java
public void execute() throws MojoExecutionException {
    Map<String, Object> ctx = mavenSession.getPluginContext(pluginDescriptor,
                                                             mavenSession.getCurrentProject());
    
    // Store state
    ctx.put("myKey", "someValue");
    
    // Retrieve in subsequent execution
    String value = (String) ctx.get("myKey");
    getLog().info("Retrieved from context: " + value);
}

```

### Creating a Derived Session with Custom Repository

Plugins can create isolated repository configurations without mutating the global session:

```java
public void execute() throws MojoExecutionException {
    Path newRepoPath = Paths.get("/tmp/custom-local-repo");
    LocalRepository newLocalRepo = mavenSession.createLocalRepository(newRepoPath);
    Session derived = mavenSession.withLocalRepository(newLocalRepo);
    
    // Resolution uses the custom repository
    ArtifactCoordinates coords = session.createArtifactCoordinates("org.apache.commons", "commons-lang3", "3.14.0", "jar");
    DownloadedArtifact resolved = derived.resolveArtifact(coords);
}

```

### Registering Global Build Listeners

Attach listeners to receive lifecycle events:

```java
public void execute() {
    mavenSession.registerListener(new ExecutionListener() {
        public void beforeSessionStart(Session session) {
            getLog().info("Session about to start");
        }
        public void afterSessionEnd(Session session, MavenResult result) {
            getLog().info("Session finished with success = " + result.isSuccess());
        }
    });
}

```

### Accessing the Full Project List

Retrieve all projects including those filtered out by `--projects`:

```java
public void execute() {
    List<Project> all = mavenSession.getAllProjects();
    getLog().info("Reactor contains " + all.size() + " projects");
}

```

### Resolving Version Ranges with the Maven 4 API

Use the new `Session` API for version resolution:

```java
VersionRange range = session.parseVersionRange("[1.0,2.0)");
List<Version> resolved = session.resolveVersionRange(
        session.createArtifactCoordinates("org.apache.maven", "maven-core", "3.9.0", "jar"),
        session.getRemoteRepositories());

```

## Summary

- **Three-layer architecture**: Maven uses `MavenSession` (legacy), `Session` (Maven 4 API), and internal implementation classes (`DefaultSession`, `SessionScope`) to coordinate builds
- **Thread-safe isolation**: The `currentProject` `ThreadLocal` and session cloning enable parallel builds without race conditions
- **State persistence**: Plugin contexts provide `ConcurrentMap` storage keyed by project and plugin, allowing state to survive across mojo executions
- **Immutability support**: Derived sessions via `withLocalRepository()` and `withRemoteRepositories()` allow repository customization without global mutation
- **Service abstraction**: The `Session` API provides `getService()` for dependency injection lookup while shielding plugins from container specifics

## Frequently Asked Questions

### What is the difference between MavenSession and Session in Maven 4?

`MavenSession` in `org.apache.maven.execution` is the legacy concrete class holding request, result, and thread-local state, used throughout Maven 3.x and maintained for backward compatibility. `org.apache.maven.api.Session` is the Maven 4 abstraction that provides a stable contract for plugin development, exposing service lookup, derived sessions, and repository operations while hiding internal DI container details.

### How does Maven handle thread safety during parallel builds?

Maven achieves thread safety by cloning the `MavenSession` for each worker thread, giving each thread its own `ThreadLocal` holder for the `currentProject` while sharing immutable data like the execution request and result. The `pluginContextsByProjectAndPluginKey` uses `ConcurrentHashMap` structures to ensure safe concurrent access to plugin state storage.

### What are plugin contexts and when should they be used?

Plugin contexts are nested `ConcurrentMap` structures (project → plugin → user data) that allow mojos to store state that must persist across multiple executions of the same plugin within the same build. Use `MavenSession.getPluginContext()` to retrieve the map for the current project and plugin combination when you need to cache expensive computations or share data between different goals of the same plugin.

### How can a plugin create a derived session with custom repository settings?

Plugins call `session.withLocalRepository(newLocalRepo)` or `session.withRemoteRepositories(newRemotes)` to create a derived session. This returns a new `Session` instance that wraps the original but overrides the specified repository methods, preserving the immutability of the original session while allowing customized artifact resolution for specific operations.