Apache Maven Verifier is deprecated and will be replaced by maven-executor.
New projects should use maven-executor. Existing projects should plan migration to maven-executor.
See Issue #186 for more details.
Maven currently has two overlapping components for running Maven programmatically:
- maven-invoker: Can only fork Maven processes
- maven-verifier: Can fork or embed Maven, but with helper methods unnecessarily coupled to execution
Both have issues:
- ❌ Different APIs for the same purpose
- ❌ Require updates when Maven CLI changes
- ❌ Heavy-handed solutions with duplicated concerns
- ❌ No unified approach for Maven 3 and Maven 4 support
- ❌ Time-consuming to maintain
- ✅ Unified API: Single, simple API without need for changes when CLI changes
- ✅ Both execution modes: Supports both "forked" and "embedded" executors
- ✅ Dependency-less: Minimal dependencies
- ✅ Maven 3.9 & 4+ support: Transparent support for both Maven versions
- ✅ Better isolation: Proper environment isolation
- ✅ Already in use: Powers Maven 4 Integration Tests
Remove:
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-verifier</artifactId>
<scope>test</scope>
</dependency>Add:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-executor</artifactId>
<version>4.0.0-rc-5</version> <!-- Use latest version -->
<scope>test</scope>
</dependency>Note: maven-executor location may change as it might be moved out of Maven 4 core to become a standalone project. Check the latest documentation.
import org.apache.maven.shared.verifier.Verifier;
import org.apache.maven.shared.verifier.VerificationException;
public class MyTest {
@Test
public void testBuild() throws Exception {
String baseDir = "/path/to/project";
Verifier verifier = new Verifier(baseDir);
// Configure
verifier.setAutoclean(false);
verifier.setMavenDebug(true);
verifier.addCliArgument("-DskipTests=true");
// Execute
verifier.addCliArgument("package");
verifier.execute();
// Verify
verifier.verifyErrorFreeLog();
verifier.verifyFilePresent("target/my-app-1.0.jar");
verifier.resetStreams();
}
}import org.apache.maven.api.cli.Executor;
import org.apache.maven.api.cli.ExecutorRequest;
import org.apache.maven.cling.executor.forked.ForkedExecutor;
import org.apache.maven.cling.executor.embedded.EmbeddedExecutor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
public class MyTest {
@Test
public void testBuild() throws Exception {
Path baseDir = Paths.get("/path/to/project");
// Choose executor type: Forked or Embedded
Executor executor = new ForkedExecutor();
// OR: Executor executor = new EmbeddedExecutor();
// Build request with CLI arguments
ExecutorRequest request = ExecutorRequest.builder()
.cwd(baseDir)
.arguments(Arrays.asList("package", "-DskipTests=true", "-X"))
.build();
// Execute
int exitCode = executor.execute(request);
// Verify
assertEquals(0, exitCode, "Build should succeed");
assertTrue(Files.exists(baseDir.resolve("target/my-app-1.0.jar")),
"JAR file should exist");
}
}| maven-verifier Concept | maven-executor Equivalent |
|---|---|
new Verifier(baseDir) |
ExecutorRequest.builder().cwd(baseDir).build() |
verifier.addCliArgument(arg) |
Add to arguments list in ExecutorRequest |
verifier.setMavenDebug(true) |
Add -X to arguments |
verifier.setAutoclean(false) |
Manage manually or via arguments |
verifier.setForkJvm(true) |
Use ForkedExecutor |
verifier.setForkJvm(false) |
Use EmbeddedExecutor |
verifier.execute() |
executor.execute(request) |
verifier.verifyErrorFreeLog() |
Check exitCode == 0 |
verifier.verifyFilePresent(path) |
Use Files.exists(Paths.get(...)) |
verifier.verifyTextInLog(text) |
Capture and parse executor output |
Before:
verifier.setEnvironmentVariable("JAVA_HOME", "/path/to/java");
verifier.setEnvironmentVariable("MAVEN_OPTS", "-Xmx1024m");After:
Map<String, String> env = new HashMap<>();
env.put("JAVA_HOME", "/path/to/java");
env.put("MAVEN_OPTS", "-Xmx1024m");
ExecutorRequest request = ExecutorRequest.builder()
.cwd(baseDir)
.arguments(args)
.environmentVariables(env)
.build();maven-verifier included many helper methods like verifyFilePresent(), verifyTextInLog(), etc. These are not part of maven-executor's core responsibility. Instead:
Extract verification to separate utilities:
public class MavenTestUtils {
public static void assertFileExists(Path base, String relativePath) {
Path file = base.resolve(relativePath);
assertTrue(Files.exists(file),
"Expected file does not exist: " + file);
}
public static void assertLogContains(String log, String expectedText) {
assertTrue(log.contains(expectedText),
"Log does not contain expected text: " + expectedText);
}
public static void assertErrorFreeLog(String log) {
assertFalse(log.contains("[ERROR]"),
"Log contains errors");
}
}Before:
verifier.setLocalRepo("/custom/repo");
verifier.setUserSettingsFile("/path/to/settings.xml");After:
ExecutorRequest request = ExecutorRequest.builder()
.cwd(baseDir)
.arguments(Arrays.asList(
"package",
"-Dmaven.repo.local=/custom/repo",
"-s", "/path/to/settings.xml"
))
.build();- Review all usages of
Verifierclass in your codebase - Update POM dependencies to use maven-executor
- Replace Verifier instantiation with ExecutorRequest builder pattern
- Convert
addCliArgument()calls to arguments list - Choose between ForkedExecutor and EmbeddedExecutor
- Replace verification methods with standard Java file checks or custom utilities
- Update environment variable configuration
- Update local repository and settings configuration
- Test thoroughly with your integration test suite
- Update documentation and comments
For large codebases, consider a gradual approach:
- Phase 1: Add maven-executor dependency alongside maven-verifier
- Phase 2: Create adapter/wrapper classes to ease transition
- Phase 3: Migrate tests module by module
- Phase 4: Remove maven-verifier dependency once all tests are migrated
For gradual migration, you can create an adapter:
public class MavenExecutorAdapter {
private final Path baseDir;
private final List<String> arguments = new ArrayList<>();
private final Map<String, String> env = new HashMap<>();
private Executor executor = new ForkedExecutor();
public MavenExecutorAdapter(String baseDir) {
this.baseDir = Paths.get(baseDir);
}
public void addCliArgument(String arg) {
arguments.add(arg);
}
public void setEnvironmentVariable(String key, String value) {
env.put(key, value);
}
public void setForkJvm(boolean fork) {
executor = fork ? new ForkedExecutor() : new EmbeddedExecutor();
}
public void execute() throws Exception {
ExecutorRequest request = ExecutorRequest.builder()
.cwd(baseDir)
.arguments(arguments)
.environmentVariables(env)
.build();
int exitCode = executor.execute(request);
if (exitCode != 0) {
throw new Exception("Maven execution failed with exit code: " + exitCode);
}
}
// Add other adapter methods as needed
}For migration questions or issues:
- Post to Maven Dev Mailing List
- Open issues on maven-executor GitHub