# How to Customize Maven Execution Behavior: CLI Flags, Embedder API, and Extension Points

> Learn how to customize Maven execution behavior using CLI flags, the Embedder API, and extension points for powerful build control.

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

---

**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`](https://github.com/apache/maven/blob/main/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java)):

1. **CLI Parsing**: `MavenCli.doMain()` reads command-line arguments, system properties, and the `.mvn/maven.config` file.
2. **Request Instantiation**: A fresh `DefaultMavenExecutionRequest` is created (see the constructor at line 75 of [`DefaultMavenExecutionRequest.java`](https://github.com/apache/maven/blob/main/DefaultMavenExecutionRequest.java)).
3. **Default Population**: `MavenExecutionRequestPopulator.populateDefaults()` enriches the request with settings from `~/.m2/settings.xml`, environment variables, and active core extensions.
4. **EventSpy Injection**: The `EventSpyDispatcher` is attached to the request so that every lifecycle phase fires observable events (configured in `MavenCli.configure()` around lines 1184-1190).
5. **Execution**: `Maven.execute()` (around line 500 in [`impl/maven-impl/src/main/java/org/apache/maven/impl/Maven.java`](https://github.com/apache/maven/blob/main/impl/maven-impl/src/main/java/org/apache/maven/impl/Maven.java)) transforms the request into a `MavenExecutionResult`.

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`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java)) provides setters for common execution parameters:

```java
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`](https://github.com/apache/maven/blob/main/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:

```java
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`](https://github.com/apache/maven/blob/main/META-INF/maven/extensions.xml):

```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`](https://github.com/apache/maven/blob/main/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java)) to inject defaults globally:

```java
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`](https://github.com/apache/maven/blob/main/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 to `request.setUserProperties()`)
- **Offline mode**: `--offline` or `-o` (sets `request.setOffline(true)`)
- **Custom settings**: `-s /path/to/settings.xml` (affects the population step)
- **Parallel builds**: `-T 4` (sets `request.setDegreeOfConcurrency(4)`)
- **Project-specific config**: Place options in `.mvn/maven.config` in 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 `MavenExecutionRequest` object created in `MavenCli` and processed by `Maven.execute()`.
- **Programmatic customization** uses the embedder API to set flags like `offline`, `degreeOfConcurrency`, and custom properties on `DefaultMavenExecutionRequest`.
- **Event interception** is achieved by implementing `EventSpy` and registering it via [`extensions.xml`](https://github.com/apache/maven/blob/main/extensions.xml), monitored by `EventSpyDispatcher`.
- **Core component replacement** allows you to provide custom `MavenExecutionRequestPopulator` implementations or other extensions via the `CoreExtension` mechanism.
- **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`](https://github.com/apache/maven/blob/main/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.