How to Customize Maven Execution Behavior: CLI Flags, Embedder API, and Extension Points
You customize Maven execution behavior by intercepting and modifying the MavenExecutionRequest object before Maven.execute() processes it, using command-line options, programmatic setters, or custom EventSpy and CoreExtension implementations.
Apache Maven's architecture follows a strict request-response pattern that makes customization straightforward once you understand the pipeline. In the apache/maven repository, the MavenCli class orchestrates the build by constructing a DefaultMavenExecutionRequest, populating it via MavenExecutionRequestPopulator, and eventually delegating to Maven.execute(). Every major step in this flow exposes setters and extension points that allow you to customize Maven execution behavior without modifying core engine code.
Understanding the Execution Pipeline
Maven's build lifecycle follows a predictable sequence exposed in org.apache.maven.cli.MavenCli (compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java):
- CLI Parsing:
MavenCli.doMain()reads command-line arguments, system properties, and the.mvn/maven.configfile. - Request Instantiation: A fresh
DefaultMavenExecutionRequestis created (see the constructor at line 75 ofDefaultMavenExecutionRequest.java). - Default Population:
MavenExecutionRequestPopulator.populateDefaults()enriches the request with settings from~/.m2/settings.xml, environment variables, and active core extensions. - EventSpy Injection: The
EventSpyDispatcheris attached to the request so that every lifecycle phase fires observable events (configured inMavenCli.configure()around lines 1184-1190). - Execution:
Maven.execute()(around line 500 inimpl/maven-impl/src/main/java/org/apache/maven/impl/Maven.java) transforms the request into aMavenExecutionResult.
Because each step exposes the request object publicly, you can inject custom logic at any point before the final execution call.
Customizing via the Embedder API
For embedded builds or CI/CD integrations, modify the DefaultMavenExecutionRequest programmatically before passing it to Maven.execute().
Setting Execution Flags
The DefaultMavenExecutionRequest class (impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java) provides setters for common execution parameters:
import org.apache.maven.DefaultMaven;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.RepositorySystem;
public class CustomMavenEmbedder {
public static void main(String[] args) throws Exception {
// Build a Maven repository system (normally done by Plexus, omitted for brevity)
RepositorySystem repoSystem = MavenRepositorySystemUtils.newRepositorySystem();
// Create a request and customize it
DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setBaseDirectory(new File("."));
request.addGoal("clean");
request.addGoal("install");
request.setOffline(true); // run offline
request.setDegreeOfConcurrency(8); // parallel builds
request.getUserProperties().setProperty("myProp", "value");
// Create Maven core and execute
DefaultMaven maven = new DefaultMaven();
MavenExecutionResult result = maven.execute(request);
if (result.hasExceptions()) {
result.getExceptions().forEach(Throwable::printStackTrace);
}
}
}
This approach allows you to programmatically control offline mode, parallelism, repository locations, and user properties before the build starts.
Intercepting Lifecycle Events with EventSpy
To observe or modify behavior during execution, implement the org.apache.maven.eventspy.EventSpy interface. The EventSpyDispatcher (impl/maven-core/src/main/java/org/apache/maven/eventspy/internal/EventSpyDispatcher.java) forwards all lifecycle events to registered spies.
Implementing a Custom EventSpy
Create a spy that logs every mojo execution:
package com.example;
import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.ExecutionEvent.Type;
public class LoggingSpy extends AbstractEventSpy {
@Override
public void onEvent(Object event) {
if (event instanceof ExecutionEvent) {
ExecutionEvent ee = (ExecutionEvent) event;
if (ee.getType() == Type.MojoStarted) {
getLogger().info(">>> Starting mojo: " + ee.getMojoExecution().getArtifactId());
}
}
}
}
Register your spy in META-INF/maven/extensions.xml:
<extensions>
<extension>
<groupId>com.example</groupId>
<artifactId>logging-spy</artifactId>
<version>1.0</version>
</extension>
</extensions>
When Maven runs, MavenCli.configure() injects the EventSpyDispatcher into the request, ensuring your spy receives every ExecutionEvent.
Replacing Core Components
For deeper customization, you can replace the MavenExecutionRequestPopulator itself or register a CoreExtension.
Custom Request Populator
Override DefaultMavenExecutionRequestPopulator (impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java) to inject defaults globally:
public class MyRequestPopulator implements MavenExecutionRequestPopulator {
@Override
public MavenExecutionRequest populateDefaults(MavenExecutionRequest request) {
// Start from the defaults
request = new DefaultMavenExecutionRequestPopulator().populateDefaults(request);
// Add a default property for all builds
request.getUserProperties().setProperty("my.default.prop", "123");
return request;
}
}
Package this as a core extension by implementing org.apache.maven.extension.CoreExtension (see api/maven-api-core/src/main/java/org/apache/maven/api/cli/extensions/CoreExtension.java) and listing it in ${user.home}/.m2/extensions.xml. This causes MavenCli to discover and use your populator instead of the built-in version.
Command-Line and Configuration File Customization
For simpler cases, you can customize Maven execution behavior without Java code:
- Goals and phases: Pass directly to
mvn(e.g.,mvn clean install) - System properties: Use
-Dkey=value(maps torequest.setUserProperties()) - Offline mode:
--offlineor-o(setsrequest.setOffline(true)) - Custom settings:
-s /path/to/settings.xml(affects the population step) - Parallel builds:
-T 4(setsrequest.setDegreeOfConcurrency(4)) - Project-specific config: Place options in
.mvn/maven.configin your project root
These options are parsed by MavenCli.doMain() and applied to the MavenExecutionRequest before population.
Summary
- Maven execution behavior is controlled by the
MavenExecutionRequestobject created inMavenCliand processed byMaven.execute(). - Programmatic customization uses the embedder API to set flags like
offline,degreeOfConcurrency, and custom properties onDefaultMavenExecutionRequest. - Event interception is achieved by implementing
EventSpyand registering it viaextensions.xml, monitored byEventSpyDispatcher. - Core component replacement allows you to provide custom
MavenExecutionRequestPopulatorimplementations or other extensions via theCoreExtensionmechanism. - CLI customization modifies the same request object through command-line flags parsed by
MavenCli.doMain().
Frequently Asked Questions
How do I run Maven builds programmatically with custom settings?
Use the embedder API by creating a DefaultMavenExecutionRequest, configuring it with setters like setOffline() and setDegreeOfConcurrency(), then passing it to DefaultMaven.execute(). This bypasses CLI parsing while respecting the same execution pipeline defined in org.apache.maven.Maven.
What is the difference between an EventSpy and a Maven plugin?
An EventSpy observes the entire build lifecycle across all projects and is registered via extensions.xml, while a plugin executes specific goals within the lifecycle. EventSpies run in the same container as Maven itself and can intercept events before any plugin executes, using the EventSpyDispatcher in impl/maven-core.
Can I change the default request population logic for all builds?
Yes. Implement a custom MavenExecutionRequestPopulator, package it as a core extension implementing CoreExtension, and register it in ~/.m2/extensions.xml. This replaces the default DefaultMavenExecutionRequestPopulator used by MavenCli to populate request defaults from settings and environment variables.
Where does Maven read project-specific execution options?
Maven reads the .mvn/maven.config file in the project root during MavenCli.doMain() parsing, applying those options to the execution request before the build begins. This is the recommended way to share custom flags (like --threads or specific profiles) across a team without wrapper scripts.
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 →