How to Debug Maven Execution Flow: A Complete Guide to Maven's Internal Mechanics

You can debug Maven execution flow by combining verbose logging (-X), dry-run mode (-DdryRun), custom EventSpy implementations, and remote JVM debugging breakpoints set in core classes like DefaultLifecycleExecutor and MojoExecutor.

Apache Maven orchestrates a sophisticated pipeline that transforms command-line arguments into an ordered sequence of plugin executions. Understanding how to debug Maven execution flow requires visibility into the core classes that manage this transformation, from CLI parsing in MavenCli to goal execution in MojoExecutor. This guide examines the actual source code from the apache/maven repository to show you exactly where and how to intercept the build process for troubleshooting.

Understanding Maven's Execution Architecture

Maven's build lifecycle follows a precise chain of responsibility, with each component handing off to the next until the final plugin goal executes. The execution flow traverses seven distinct stages, from initial CLI parsing through project resolution to the final mojo invocation. Each stage offers specific hooks for debugging, allowing you to pinpoint exactly where a build hangs, why a plugin is skipped, or how the execution plan is constructed.

The 7 Stages of Maven Execution Flow

1. CLI Parsing and Request Building

The entry point class org.apache.maven.cli.MavenCli (located in compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java) handles the initial transformation of command-line arguments into a MavenExecutionRequest. This class processes the -X (debug) and -e (full stacktrace) flags, populating the request object with logging preferences and build options before the core execution begins.

2. Session Construction

The org.apache.maven.DefaultMaven class (in impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java) constructs the MavenSession that serves as the execution context for the entire build. It delegates to MavenExecutionRequestPopulator and MavenExecutionRequestBuilder to initialize the session with the repository system, project builder, and class realm.

3. Effective POM Resolution

The org.apache.maven.model.building.DefaultModelBuilder (in compat/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java) reads the root pom.xml, interpolates properties, merges parent POMs, and validates the model structure. This stage produces an EffectiveModel that ultimately becomes a MavenProject used throughout the lifecycle.

4. Project Graph Building

The org.apache.maven.project.DefaultProjectBuilder (in impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java) detects multi-module projects and their inter-dependencies, calculating the precise order in which modules must be built. It returns a list of MavenProject objects representing the reactor, handling circular dependency detection and module ordering.

5. Execution Plan Creation

The org.apache.maven.lifecycle.DefaultLifecycleExecutor (in impl/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java) determines which plugins must execute for the requested lifecycle phases. It collaborates with org.apache.maven.lifecycle.MavenExecutionPlan (in impl/maven-core/src/main/java/org/apache/maven/lifecycle/MavenExecutionPlan.java) to walk the lifecycle definition, resolve plugin descriptors, and generate an ordered list of ExecutionPlanItems.

6. Mojo Execution

The internal MojoExecutor class (in impl/maven-core/src/main/java/org/apache/maven/execution/MojoExecutor.java) iterates through the execution plan and invokes Mojo.execute() for each goal. It handles forked lifecycles, evaluates skip conditions, and enforces @requiresProject checks before delegating to the plugin code.

7. Event Broadcasting

Throughout execution, Maven fires org.apache.maven.eventspy.ExecutionEvent objects (defined in api/maven-event-api/src/main/java/org/apache/maven/eventspy/ExecutionEvent.java) to signal lifecycle progress. Custom EventSpy implementations can intercept these events to monitor start, success, and failure states for each mojo execution.

Techniques to Debug Maven Execution Flow

Verbose Logging and Dry Run Mode

The -X flag enables verbose debug logging throughout the entire codebase, dumping internal state including effective POMs, repository sessions, and full stack traces. The -DdryRun flag constructs the full MavenExecutionPlan in DefaultLifecycleExecutor but stops before MojoExecutor runs any goals, revealing the exact plugin order without side effects.

mvn -X -DdryRun clean install

Remote JVM Debugging

Set the MAVEN_OPTS environment variable to enable the Java Debug Wire Protocol before launching Maven. With this configuration, Maven suspends at startup and waits for a debugger connection on port 8000, allowing you to set breakpoints in DefaultLifecycleExecutor.calculateExecutionPlan() or MojoExecutor.execute() to step through the actual execution logic.

export MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8000"
mvn clean install

Custom EventSpy Implementation

Create a class extending org.apache.maven.eventspy.AbstractEventSpy and package it as a JAR with a META-INF/maven/event-spy.xml descriptor file. Deploy this JAR to your build using the -Dmaven.ext.class.path argument to intercept ExecutionEvent objects at the API level, allowing you to log timing data or inspect the MavenSession without modifying project POMs.

import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.execution.ExecutionEvent;

public class ExecutionPlanPrinter extends AbstractEventSpy {
    @Override
    public void onEvent(Object event) {
        if (event instanceof ExecutionEvent) {
            ExecutionEvent ev = (ExecutionEvent) event;
            if (ev.getType() == ExecutionEvent.Type.MojoStarted) {
                System.out.println("Executing: " + 
                    ev.getMojoExecution().getArtifactId() + ":" +
                    ev.getMojoExecution().getGoal());
            }
        }
    }
}

Activate the spy with:

mvn -Dmaven.ext.class.path=/path/to/your-spy.jar clean install

MavenSession Inspection

Access the MavenSession object through an EventSpy to inspect project state via getProjects(), getCurrentProject(), and getPluginContext() methods. Enable reactor debugging with -Dmaven.reactor.debug=true to expose module ordering and up-to-date checks, revealing why Maven skips certain modules or reorders the build sequence.

Summary

  • Maven execution flow traverses seven stages from MavenCli argument parsing through MojoExecutor goal invocation.
  • Verbose mode (-X) and dry run (-DdryRun) provide immediate visibility into execution plans without custom code.
  • Remote debugging via MAVEN_OPTS allows breakpoint inspection of DefaultLifecycleExecutor and MojoExecutor.
  • Custom EventSpy implementations hook into ExecutionEvent broadcasts to monitor mojo start and completion events.
  • MavenSession inspection through EventSpy reveals project graphs, plugin contexts, and reactor ordering.

Frequently Asked Questions

How do I see exactly which plugins are executing in my Maven build?

Use the -DdryRun flag to print the MavenExecutionPlan without running goals, or implement a custom EventSpy that logs ExecutionEvent.Type.MojoStarted events. The dry run outputs the ordered list of ExecutionPlanItems from DefaultLifecycleExecutor, while the EventSpy provides real-time confirmation as MojoExecutor invokes each goal. Both methods require no modifications to your project POM.

What is the difference between -X debug mode and -DdryRun?

The -X flag enables verbose logging throughout the entire codebase, showing internal state dumps, dependency resolution, and plugin configuration, but actually executes the build. The -DdryRun flag constructs the full execution plan in MavenExecutionPlan but stops before MojoExecutor runs any goals, making it safe for inspecting build logic without side effects.

How can I debug why Maven hangs during a specific lifecycle phase?

Attach a remote JVM debugger using MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8000" and set breakpoints in DefaultLifecycleExecutor.execute() or MojoExecutor.execute(). When the build suspends, inspect the current ExecutionPlanItem to identify which mojo is active. Alternatively, use a custom EventSpy to log timestamps between MojoStarted and MojoSucceeded events to identify long-running goals.

Can I debug Maven execution without modifying the project POM or source code?

Yes, external debugging requires no POM changes. Use -X for immediate logging, -DdryRun to validate execution plans, or package a custom EventSpy in a separate JAR and activate it via -Dmaven.ext.class.path. Remote debugging through MAVEN_OPTS also requires zero project modifications, allowing you to step through apache/maven core classes like DefaultMaven and DefaultProjectBuilder without touching the project being built.

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 →