How Maven Handles Parallel Builds and Concurrency: A Deep Dive into the Source Code
Maven parallel builds are controlled by the -T flag, which configures a fixed-size thread pool that executes lifecycle phases across modules concurrently while enforcing thread-safety checks and respecting the reactor failure strategy.
Apache Maven supports parallel builds through a sophisticated concurrency engine that transforms the -T command-line option into a managed thread pool. This mechanism enables simultaneous execution of independent project modules while maintaining dependency order and validating plugin thread-safety. Understanding this system requires examining the core execution classes in the apache/maven repository that parse, propagate, and enforce parallelism throughout the build lifecycle.
Parsing the -T Option and Calculating Concurrency
Maven initiates parallel builds by parsing the -T (threads) argument during CLI invocation. The parsing logic converts values like 4, 1C (one thread per CPU core), or 2C (two threads per core) into an integer representing the degree of concurrency.
In impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java (lines 293-298), the invoker reads the flag and populates the execution request:
// From MavenInvoker.java
request.setDegreeOfConcurrency(threads);
The calculation logic resides in compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java (lines 1401-1404), which evaluates thread specifications relative to available processors. The parsed value is then stored in MavenExecutionRequest and defaults to 1 in DefaultMavenExecutionRequest.java.
Thread Pool Creation and Session Propagation
Once parsed, Maven determines the actual thread pool size by comparing the requested degree of concurrency against the number of projects in the reactor. This calculation appears in impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java (lines 100-114):
int threads = Math.min(session.getRequest().getDegreeOfConcurrency(),
session.getProjects().size());
session.setParallel(threads > 1);
The MultiThreadedBuilder.java class (lines 84-90) performs similar propagation for each ProjectSegment in multi-module builds. Maven then instantiates a fixed-size thread pool using Executors.newFixedThreadPool with a custom BuildThreadFactory, wrapped in a PhasingExecutor that manages concurrent lifecycle step execution.
Weave-Mode Execution and Dependency Scheduling
Maven's default weave strategy, implemented in BuildPlanExecutor, schedules build steps (setup, mojo execution, teardown) across projects while respecting inter-project dependencies. The executePlan() method (lines 81-92) identifies ready steps, while processStep() (lines 65-87) determines whether to schedule or skip them based on completion status of predecessor steps.
The PhasingExecutor runs these steps concurrently, but only after all dependencies have completed successfully. This ensures that module A compiles before module B (if B depends on A) while allowing independent modules to build simultaneously.
Thread-Safety Validation
Before parallel execution begins, Maven validates that every mojo (plugin goal) is marked as thread-safe. The checkThreadSafety() method in BuildPlanExecutor.java (lines 108-117) collects executions lacking the @threadSafe annotation or Maven-4-compatible markers.
If unsafe mojos are detected, Maven logs a detailed warning identifying the specific plugins and versions. While the build may continue depending on configuration, the system strongly encourages upgrading to thread-safe plugin versions to prevent race conditions and corrupted build outputs.
Failure Handling in Parallel Builds
Parallel execution respects the reactor failure behavior configured via command-line flags (-ff fail-fast, -fae fail-at-end, etc.). The handleBuildError() method in BuildPlanExecutor.java (lines 90-106) updates the reactor status when a project fails, which influences subsequent scheduling decisions.
In failFast mode, the error handler halts further parallel work immediately. For failAtEnd, Maven completes currently running projects but skips dependent modules, aggregating all failures for the final report.
Configuration API and Programmatic Access
The public API exposes thread configuration through MavenOptions.java (lines 61-66) in api/maven-api-cli, defining Optional<String> threads(). Developers can query this value programmatically within plugins or extensions:
MavenSession session = ...;
String threads = session.getRequest()
.getUserProperties()
.getProperty("threads");
System.out.println("Parallel threads: " + threads);
Typical command-line usage includes:
# Run with four worker threads
mvn -T 4 clean install
# Use one thread per CPU core
mvn -T 1C clean verify
# Two threads per core
mvn -T 2C clean package
Summary
- Maven parallel builds are activated via the
-Tflag, supporting absolute numbers (4) or CPU-relative values (1C,2C). - Thread pool sizing occurs in
BuildPlanExecutor, calculating the minimum of requested threads and available projects. - Dependency ordering is preserved through the weave-mode execution strategy, which uses
PhasingExecutorto schedule steps only after predecessors complete. - Thread-safety verification happens before execution in
checkThreadSafety(), warning about non-thread-safe plugins. - Failure handling integrates with reactor strategies via
handleBuildError(), respectingfailFast,failAtEnd, andfailNeverconfigurations. - Programmatic access is available through
MavenOptions.threads()andMavenExecutionRequestproperties.
Frequently Asked Questions
How do I enable parallel builds in Maven?
Use the -T or --threads command-line option followed by a number or CPU multiplier. For example, mvn -T 4 clean install uses four threads, while mvn -T 1C clean install allocates one thread per CPU core. This value propagates through MavenExecutionRequest and configures the thread pool in BuildPlanExecutor.
What does the C suffix mean in the -T option?
The C suffix indicates "per CPU core." Maven calculates the actual thread count by multiplying the number preceding C by the available processor cores. For instance, -T 2C on an 8-core machine results in 16 threads. This calculation occurs in MavenCli.java (lines 1401-1404) before being stored in the execution request.
Are Maven plugins thread-safe by default?
No. Maven validates thread-safety before parallel execution by checking for the @threadSafe annotation or Maven 4 compatibility markers in BuildPlanExecutor.checkThreadSafety(). If unsafe plugins are detected, Maven logs a warning listing the specific goals that may cause concurrency issues, though the build may continue depending on configuration.
How does Maven handle build failures during parallel execution?
Maven respects the configured reactor failure strategy (-ff, -fae, -fn). The handleBuildError() method in BuildPlanExecutor updates the reactor status, which determines whether to halt immediately (failFast), continue with independent projects (failAtEnd), or ignore failures entirely (failNever). This ensures consistent behavior regardless of which thread encounters the error.
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 →