Maven Exec Plugin
last modified November 8, 2026
In this article we show how to run Java programs and external commands from Maven with the Exec plugin. The examples use Maven 3.10, Java 25, and version 3.6.4 of the plugin.
The Exec plugin, maintained by the MojoHaus project, is handy during
development: it runs a class with the project classpath without building a
JAR file or typing a long java command. It has two goals that
work very differently:
exec:javaruns a Java class inside the JVM that runs Mavenexec:execstarts an external program, which can also be a newjavaprocess, as a separate operating system process
The example project
The plugin is declared in the POM with its version. The plugin-level
configuration sets the main class for exec:java. The executions
are explained later in the article.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>exec-example</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.4</version>
<configuration>
<mainClass>com.example.App</mainClass>
</configuration>
<executions>
<execution>
<id>with-config</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<arguments>
<argument>input.txt</argument>
<argument>output.txt</argument>
</arguments>
<systemProperties>
<systemProperty>
<key>app.name</key>
<value>ConfiguredApp</value>
</systemProperty>
</systemProperties>
</configuration>
</execution>
<execution>
<id>run-java</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-Dapp.name=ExecDemo</argument>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.App</argument>
<argument>alpha</argument>
</arguments>
<environmentVariables>
<APP_ENV>development</APP_ENV>
</environmentVariables>
<workingDirectory>${project.build.directory}</workingDirectory>
</configuration>
</execution>
<execution>
<id>status</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.Status</argument>
<argument>3</argument>
</arguments>
<successCodes>
<successCode>0</successCode>
<successCode>3</successCode>
</successCodes>
</configuration>
</execution>
<execution>
<id>debug</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005</argument>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.App</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The POM pins version 3.6.4 of the plugin. Without the declaration, Maven
could still run mvn exec:java, because it resolves the
exec prefix to the latest version of the plugin, but such a
build is not reproducible.
package com.example;
import java.nio.file.Path;
import java.util.Arrays;
public class App {
public static void main(String[] args) {
System.out.println("Hello from " + App.class.getName());
System.out.println("Arguments: " + Arrays.toString(args));
System.out.println("app.name: " + System.getProperty("app.name", "not set"));
System.out.println("APP_ENV: " + System.getenv().getOrDefault("APP_ENV", "not set"));
System.out.println("Working directory: " + Path.of("").toAbsolutePath().getFileName());
System.out.println("JVM started with: " + System.getProperty("sun.java.command"));
}
}
The application prints its arguments, a system property, an environment
variable, the name of the working directory, and the command that started the
JVM. The sun.java.command property contains the main class and
the arguments of the java command.
exec:java does not compile
The exec:java goal is not bound to any phase and does not compile
anything. On a fresh project it fails, because there are no compiled classes
yet.
$ mvn exec:java [ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.6.4:java (default-cli) on project exec-example: An exception occurred while executing the Java class. com.example.App -> [Help 1] ...
Running a class with exec:java
Therefore we combine the goal with the compile phase. The
-q option hides the Maven log, so only the output of the
application is shown. The last line reveals that the class runs in the JVM of
Maven: the JVM was started with the Maven launcher class, and our class was
called later from Maven's process. The working directory is the project
directory.
$ mvn -q compile exec:java Hello from com.example.App Arguments: [] app.name: not set APP_ENV: not set Working directory: app JVM started with: org.codehaus.plexus.classworlds.launcher.Launcher -q compile exec:java
Choosing the main class
The exec.mainClass user property sets the main class from the
command line. However, user properties only fill in parameters that the POM
does not set. In our project, the mainClass parameter is
configured, so the property is ignored and App runs again. In a
project without that configuration, here exit, the property
selects the Tool class.
package com.example;
public class Tool {
public static void main(String[] args) {
System.out.println("Running the tool");
}
}
A second class with a main method.
$ mvn -q exec:java -Dexec.mainClass=com.example.Tool Hello from com.example.App Arguments: [] app.name: not set APP_ENV: not set Working directory: app JVM started with: org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java -Dexec.mainClass=com.example.Tool $ cd ../exit $ mvn -q exec:java -Dexec.mainClass=com.example.Tool Running the tool
Arguments and system properties
The exec.args property passes arguments to the program. Quotes
group words into a single argument. Because exec:java runs in
the Maven JVM, every -D option given to Maven is also a system
property of our program; here it sets app.name. Environment
variables come from the shell that started Maven; exec:java
cannot change them.
$ mvn -q exec:java -Dexec.args="one two 'three four'" -Dapp.name=Demo Hello from com.example.App Arguments: [one, two, three four] app.name: Demo APP_ENV: not set Working directory: app JVM started with: org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java -Dexec.args=one two 'three four' -Dapp.name=Demo
Configuring arguments in the POM
Arguments and system properties can be configured in the POM. The
with-config execution of the java goal is not bound
to a phase; we run it from the command line with the
goal@execution-id syntax.
<id>with-config</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<arguments>
<argument>input.txt</argument>
<argument>output.txt</argument>
</arguments>
<systemProperties>
<systemProperty>
<key>app.name</key>
<value>ConfiguredApp</value>
</systemProperty>
</systemProperties>
</configuration>
The arguments element lists the arguments one by one, so no
quoting is needed. The systemProperties element sets system
properties for the duration of the run.
Arguments from exec.args replace the configured ones, as the
second command shows.
$ mvn -q exec:java@with-config Hello from com.example.App Arguments: [input.txt, output.txt] app.name: ConfiguredApp APP_ENV: not set Working directory: app JVM started with: org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java@with-config $ mvn -q exec:java@with-config -Dexec.args="x y" Hello from com.example.App Arguments: [x, y] app.name: ConfiguredApp APP_ENV: not set Working directory: app JVM started with: org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java@with-config -Dexec.args=x y
System.exit in exec:java
Since exec:java shares the JVM with Maven, a call to
System.exit terminates Maven itself.
package com.example;
public class Status {
public static void main(String[] args) {
int code = args.length > 0 ? Integer.parseInt(args[0]) : 0;
System.out.println("Exiting with code " + code);
System.exit(code);
}
}
The program exits with the code given as an argument.
In the first run, Maven ends right after the program's output, without the
usual build summary, and the shell gets the exit code of the program. With
the exec.blockSystemExit option, the plugin intercepts the call:
a non-zero code fails the build in a regular way, and a zero code lets the
build continue.
$ mvn exec:java -Dexec.mainClass=com.example.Status -Dexec.args=3 [INFO] --- exec:3.6.4:java (default-cli) @ exec-exit --- Exiting with code 3 $ echo $? 3 $ mvn exec:java -Dexec.mainClass=com.example.Status -Dexec.args=3 -Dexec.blockSystemExit Exiting with code 3 [ERROR] BUILD FAILURE [ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.6.4:java (default-cli) on project exec-exit: An exception occurred while executing the Java class. System::exit was called with return code 3 -> [Help 1] ...
Running programs with exec:exec
The exec:exec goal runs an executable in a new process. To run
our class in a separate JVM, we call java and pass the project
classpath.
<id>run-java</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-Dapp.name=ExecDemo</argument>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.App</argument>
<argument>alpha</argument>
</arguments>
<environmentVariables>
<APP_ENV>development</APP_ENV>
</environmentVariables>
<workingDirectory>${project.build.directory}</workingDirectory>
</configuration>
The <classpath/> element inside arguments
expands to the runtime classpath of the project. Options placed before the
class name, such as -Dapp.name=ExecDemo, are JVM options. The
environmentVariables element sets variables for the new process,
and workingDirectory sets its working directory. These
parameters exist only for exec:exec.
This time the JVM was started directly with our class, the environment
variable is set, and the working directory is target.
$ mvn exec:exec@run-java [INFO] --- exec:3.6.4:exec (run-java) @ exec-example --- Hello from com.example.App Arguments: [alpha] app.name: ExecDemo APP_ENV: development Working directory: target JVM started with: com.example.App alpha [INFO] BUILD SUCCESS ...
Exit codes
The exec:exec goal fails the build when the program returns a
non-zero exit code. The successCodes parameter lists the codes
that count as success.
<id>status</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.Status</argument>
<argument>3</argument>
</arguments>
<successCodes>
<successCode>0</successCode>
<successCode>3</successCode>
</successCodes>
</configuration>
The Status program exits with code 3, which is listed as a
success code, so the build succeeds.
$ mvn exec:exec@status [INFO] --- exec:3.6.4:exec (status) @ exec-example --- Exiting with code 3 [INFO] BUILD SUCCESS ...
Debugging
JVM options, such as the debugging agent, can be passed only to a separate
JVM, so debugging is done with exec:exec.
<id>debug</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005</argument>
<argument>-classpath</argument>
<classpath/>
<argument>com.example.App</argument>
</arguments>
</configuration>
The JDWP agent listens on port 5005, where a debugger such as IntelliJ IDEA,
Eclipse, or jdb can attach. With suspend=n the
program starts immediately; suspend=y would make it wait until a
debugger connects. To debug code run by exec:java, we would have
to start Maven itself with the agent, for example through the
MAVEN_OPTS variable.
$ mvn -q exec:exec@debug Listening for transport dt_socket at address: 5005 Hello from com.example.App Arguments: [] app.name: not set APP_ENV: not set Working directory: app JVM started with: com.example.App
Running a script during the build
An execution with a phase runs automatically as part of the
build. The following project runs a shell script in the
generate-resources phase.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.4</version>
<executions>
<execution>
<id>build-info</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>bash</executable>
<arguments>
<argument>${project.basedir}/scripts/build-info.sh</argument>
<argument>${project.build.outputDirectory}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
The execution passes the script and the output directory for classes as
arguments. The executable must be on the PATH.
#!/usr/bin/env bash out="$1/build-info.properties" mkdir -p "$1" echo "build.os=$(uname -s)" > "$out" echo "build.date=$(date -u +%F)" >> "$out" echo "Wrote $out"
The script writes a properties file into the directory given as the first argument.
The script runs before the resources are copied and the code is compiled.
The file it created in target/classes is included in the JAR.
$ mvn package [INFO] --- exec:3.6.4:exec (build-info) @ exec-script --- Wrote /home/user/script/target/classes/build-info.properties [INFO] --- resources:3.5.0:resources (default-resources) @ exec-script --- [INFO] --- compiler:3.16.0:compile (default-compile) @ exec-script --- [INFO] --- jar:3.5.1:jar (default-jar) @ exec-script --- [INFO] BUILD SUCCESS ... $ unzip -p target/exec-script-1.0.0.jar build-info.properties build.os=Linux build.date=2026-11-08
Best practices
- Declare the plugin with a fixed version in the POM.
- Use
exec:javafor quick runs during development; runcompilebefore it. - Use
exec:execwhen the program needs its own JVM options, environment variables, working directory, or when it callsSystem.exit. - Prefer the
argumentslist in the POM overexec.argsfor configurations that are shared with others. - Remember that executions bound to a phase run in every build; keep them fast and make sure the executables exist on all build machines.
Source
In this article we have shown how to run Java programs and external commands from Maven with the Exec plugin.
Author
List all Java tutorials.