How Maven Handles Workspace Resolution and Multi-Module Projects: Inside the Reactor Architecture

Maven handles workspace resolution and multi-module projects by treating the build as a reactor—an in-memory collection of all modules that allows dependencies to be resolved directly from source without requiring intermediate installation to the local repository.

The apache/maven source code implements this through a sophisticated workspace reader infrastructure that checks the reactor before consulting external repositories. When you build a multi-module project, Maven constructs a MavenSession containing all projects, sorts them according to dependency relationships, and uses a ReactorReader to satisfy inter-module dependencies immediately.

Understanding the Maven Reactor

What Is the Reactor?

The reactor is Maven's internal representation of a multi-module build. When Maven starts, it reads the top-level pom.xml and instantiates all declared modules as MavenProject objects, storing them in the current MavenSession. You can access this collection via MavenSession#getAllProjects(), which returns the sorted list of projects that constitute the build.

Reactor Construction and Sorting

Before execution begins, Maven must determine the correct build order. The DefaultProjectSorter in impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectSorter.java analyzes the dependency graph produced by MavenProject#getDependencies() and sorts modules so that upstream dependencies are built before downstream consumers. This ensures that when module B depends on module A, Maven compiles and packages A before attempting to resolve B's dependencies.

Workspace Resolution Mechanics

Workspace resolution is the mechanism that allows Maven to resolve artifacts from the current build rather than the local repository. This is essential for multi-module development, where you want changes in one module to be immediately visible to others without running mvn install.

The Workspace Reader Interface

At the core of this system is MavenWorkspaceReader, an extension of Eclipse Aether's WorkspaceReader interface defined in impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenWorkspaceReader.java. This interface provides methods to locate artifact descriptors and models within the current workspace.

ReactorReader and In-Memory Lookup

The concrete implementation that knows about the current reactor is ReactorReader, located in impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java. When Maven resolves a dependency, it queries the ReactorReader via findModel(Artifact). If the requested artifact belongs to a project in the current MavenSession, the reader returns the in-memory Model immediately, bypassing the local repository entirely.

This happens early in the resolution process. The DefaultProjectBuilder in impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java invokes the workspace reader when parsing POMs or resolving parent projects, allowing parent-child relationships and sibling dependencies to be established directly from the reactor.

Chained Resolution with MavenChainedWorkspaceReader

Maven supports multiple workspace readers through MavenChainedWorkspaceReader in impl/maven-core/src/main/java/org/apache/maven/resolver/MavenChainedWorkspaceReader.java. This class chains the primary ReactorReader with additional readers supplied by IDEs or extensions. The chain is consulted in order: first the reactor, then IDE-specific readers, ensuring that in-project sources take precedence while allowing external workspaces to be resolved.

The workspace reader is injected into the execution via MavenExecutionRequest#setWorkspaceReader and stored in DefaultMavenExecutionRequest in impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java, then propagated to the RepositorySystemSession used throughout the build.

Multi-Module Dependency Resolution

Project Building and Inter-Module Dependencies

When DefaultProjectBuilder parses each module's POM, it resolves dependencies by querying the session's workspace reader. If a dependency points to another project in the reactor, the builder calls MavenWorkspaceReader.findModel(Artifact) and receives the model directly. This allows Maven to construct the dependency graph using the current source state rather than previously installed artifacts.

Version Resolution Against the Reactor

The version resolution mechanism also consults the workspace reader. DefaultVersionResolver in impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionResolver.java and DefaultVersionRangeResolver check the reactor when resolving specific versions or version ranges. This enables snapshot handling and version mediation to work correctly against the in-memory projects, ensuring that the latest source changes are always reflected in the dependency resolution.

Similarly, DefaultArtifactDescriptorReader in impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java uses the workspace reader to obtain artifact descriptors, allowing complete dependency metadata to be retrieved from the reactor without disk access.

Partial Builds and Reactor Subsets

Maven supports building subsets of the reactor using command-line flags. When you run:

mvn -pl module-a,module-c -am clean install

Maven still constructs the full reactor but marks non-selected modules as inactive. The -pl flag selects specific modules, while -am (also-make) includes their upstream dependencies. The ReactorReader still provides models for all projects in the session, including inactive ones, ensuring that dependencies of selected modules can be satisfied even if those dependencies aren't being built in this execution.

IDE Integration and Extended Workspace Readers

IDEs like IntelliJ IDEA and Eclipse provide their own workspace readers that integrate with Maven's resolution chain. These readers are added to the MavenChainedWorkspaceReader and consulted after the reactor reader. This allows IDEs to resolve artifacts that exist in the IDE workspace but may not be part of the current Maven reactor, while maintaining the priority of the current build's modules.

Inspecting the Reactor Programmatically

You can access the reactor from within a Maven plugin to inspect the build structure. The following Mojo lists all projects in the current session:

@Mojo(name = "list-reactor", threadSafe = true, requiresProject = false)
public class ListReactorMojo extends AbstractMojo {

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

    public void execute() throws MojoExecutionException {
        getLog().info("Reactor size: " + session.getAllProjects().size());
        for (MavenProject p : session.getAllProjects()) {
            getLog().info("- " + p.getGroupId() + ":" + p.getArtifactId() + ":" + p.getVersion()
                           + "  (execution root: " + p.isExecutionRoot() + ")");
        }
    }
}

This plugin reads the MavenSession (which contains the sorted reactor via DefaultMavenSession in impl/maven-embedder/src/main/java/org/apache/maven/embeddable/DefaultMavenSession.java) and lists each project, indicating whether it is the execution root.

To manually check if an artifact resolves against the workspace reader (as Maven does internally):

RepositorySystemSession repoSession = session.getRepositorySession();
WorkspaceReader wsReader = repoSession.getWorkspaceReader(); // actually a MavenChainedWorkspaceReader

Artifact dep = new DefaultArtifact("com.example:module-b:1.0-SNAPSHOT");
if (wsReader instanceof MavenWorkspaceReader mws) {
    Model model = mws.findModel(dep);
    if (model != null) {
        // The dependency is satisfied by a project inside the reactor.
        getLog().info("Found reactor project: " + model.getId());
    }
}

This mirrors the calls in DefaultArtifactDescriptorReader and DefaultVersionResolver, where the workspace reader is consulted before checking local or remote repositories.

Summary

  • Maven treats multi-module builds as a reactor: All modules are instantiated as MavenProject objects and stored in the MavenSession, accessible via getAllProjects().
  • Workspace resolution prioritizes the reactor: The ReactorReader implements MavenWorkspaceReader to provide in-memory models for dependencies that exist in the current build, eliminating the need for intermediate mvn install steps.
  • Sorting ensures correct build order: DefaultProjectSorter orders projects based on the dependency graph from MavenProject#getDependencies(), ensuring upstream modules build first.
  • Chained readers support extensibility: MavenChainedWorkspaceReader combines the reactor reader with IDE-specific readers, allowing flexible workspace resolution.
  • Partial builds maintain full reactor knowledge: Even with -pl flags, Maven constructs the complete reactor, allowing dependencies to be resolved from inactive modules.

Frequently Asked Questions

How does Maven resolve dependencies between modules without installing them?

Maven uses the ReactorReader class in impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java to implement MavenWorkspaceReader. When resolving a dependency, Maven consults this reader first; if the artifact belongs to a project in the current MavenSession, the reader returns the in-memory Model directly, bypassing the local repository entirely.

What determines the build order in a multi-module Maven project?

The build order is determined by DefaultProjectSorter in impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectSorter.java. This component analyzes the dependency graph produced by MavenProject#getDependencies() and sorts modules so that each module is built only after its required upstream dependencies are compiled and packaged.

Can I build only specific modules while still resolving dependencies from the rest of the project?

Yes. Using the -pl (projects list) flag combined with -am (also-make), you can build a subset of modules while Maven maintains the full reactor in memory. The ReactorReader still provides access to models for all projects in the session, including those not being built, ensuring that inter-module dependencies resolve correctly.

How do IDEs integrate with Maven's workspace resolution?

IDEs provide custom implementations of WorkspaceReader that are chained with the ReactorReader via MavenChainedWorkspaceReader in impl/maven-core/src/main/java/org/apache/maven/resolver/MavenChainedWorkspaceReader.java. These IDE-specific readers are consulted after the reactor reader, allowing the IDE to resolve artifacts from its own workspace while maintaining the priority of the current Maven build.

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 →