Compare exec-maven-plugin and maven-antrun-plugin for binding a custom step to a phase. When would you choose each, and what are the trade-offs?
answer
- exec:exec forks external, exec:java same JVM
- antrun = inline Ant tasks
- PATH dependency hurts reproducibility
- antrun overuse = anti-pattern
- last resort vs real plugin
basics
~20 sexec-maven-plugin runs an external program (exec:exec) or a Java main on the project classpath (exec:java). maven-antrun-plugin runs inline Ant tasks (copy, mkdir, echo, etc.). Use exec for one external command/Java, antrun for small file/scripting glue.
solid answer
~50 sBoth are 'glue' plugins for steps Maven has no first-class goal for, but they differ in nature. **exec-maven-plugin** has two goals: `exec:exec` forks an external process (any binary, e.g. npm, protoc, a shell script) with explicit `<executable>`/`<arguments>`; `exec:java` runs a Java `main()` *in the same JVM* on the project's classpath without forking. **maven-antrun-plugin** embeds an Ant `<target>` so you can use Ant's rich task library (copy, move, mkdir, replace, concat, conditionals) declaratively in the pom. Choose exec when you must invoke a real external tool or run application Java; choose antrun for portable filesystem/manipulation glue without depending on shell-specific commands. Trade-offs: antrun is OS-portable but verbose and a known anti-pattern when overused (it smuggles imperative logic into the pom); exec:exec is simple but ties the build to whatever is installed on PATH, hurting reproducibility. Both are last resorts versus a proper plugin.
code
xml · 19 lines<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>gen-proto</id>
<phase>generate-sources</phase>
<goals><goal>exec</goal></goals>
<configuration>
<executable>protoc</executable>
<arguments>
<argument>--java_out=${project.build.directory}/generated-sources</argument>
<argument>api.proto</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>go deeper
Know exec runs a program and antrun runs Ant tasks like copy/echo.
Bind exec:exec / antrun:run to a phase with arguments and a target; know exec:java vs exec:exec exists.
Weigh PATH dependence vs portability, JVM sharing in exec:java, and when antrun becomes a smell.
Set policy: pin/vendor external tools for reproducible CI, cap antrun usage, and graduate recurring glue into a shared Mojo.
## The problem they solve Maven's lifecycle binds *goals* to *phases*. Sometimes you need a step with no existing goal — call a code generator, run `npm install`, copy a file, fail the build on a condition. Rather than write a plugin, two general-purpose plugins let you inline the step. ## exec-maven-plugin (org.codehaus.mojo) Two goals: - **exec:exec** — forks a *separate* OS process. You provide `<executable>` (e.g. `protoc`, `node`, `bash`) and `<arguments>`. It is language-agnostic. Because it forks, it does **not** get the Maven classpath unless you build it yourself; there is a special `%classpath` token to pass it. - **exec:java** — runs a Java `<mainClass>` in the *same* JVM as Maven, on the project's runtime/test classpath. No fork, fast, but a `System.exit` or rogue thread can affect the build, and it shares Maven's JVM. ```xml <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.5.0</version> <executions> <execution> <id>npm-build</id> <phase>generate-resources</phase> <goals><goal>exec</goal></goals> <configuration> <executable>npm</executable> <arguments><argument>run</argument><argument>build</argument></arguments> <workingDirectory>${project.basedir}/frontend</workingDirectory> </configuration> </execution> </executions> </plugin> ``` ## maven-antrun-plugin (org.apache.maven.plugins) One main goal, `antrun:run`, which executes an embedded Ant `<target>`. You get Ant's declarative task set: `<copy>`, `<move>`, `<mkdir>`, `<delete>`, `<replace>`, `<concat>`, `<echo>`, `<fail>`, plus conditionals. Maven properties are available, and you can export Ant-set properties back via `exportAntProperties`. ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>stamp-build</id> <phase>prepare-package</phase> <goals><goal>run</goal></goals> <configuration> <target> <mkdir dir="${project.build.directory}/meta"/> <echo file="${project.build.directory}/meta/build.txt">v=${project.version}</echo> </target> </configuration> </execution> </executions> </plugin> ``` ## Choosing - Need to run an installed external tool / shell script → **exec:exec**. - Need to run your own Java main on the project classpath quickly → **exec:java** (mind shared JVM / System.exit). - Need portable file shuffling, templating, conditional fail, with no external binary → **antrun**. ## Trade-offs and reproducibility - **exec:exec** depends on the host having the tool on PATH and the right version → builds become non-reproducible / machine-specific. Mitigate by pinning versions and documenting prerequisites, or using a wrapper (e.g. frontend-maven-plugin which downloads node). - **antrun** is portable (Ant ships with the plugin) but tempts you to embed sprawling imperative logic in XML — widely considered a smell. If a target grows, extract it into a real plugin or a script invoked via exec. - **exec:java** sharing the Maven JVM means heavy memory use or `System.exit(0)` can corrupt the reactor; for isolation prefer exec:exec with `java` as the executable. All three are pragmatic escape hatches; the cleaner long-term answer for repeated logic is a small Mojo (custom plugin).
- What's the key runtime difference between exec:java and exec:exec?exec:java runs a Java main in Maven's own JVM on the project classpath (fast, no fork, but shares the JVM and is vulnerable to System.exit/threads). exec:exec forks a brand-new OS process with no Maven classpath unless you pass %classpath.
- Why is heavy use of antrun considered an anti-pattern?It smuggles imperative build logic into the declarative pom as XML, which is hard to read, test, and reuse; the recommended path for non-trivial logic is a real Maven plugin (Mojo) or an external script.
- How do exec:exec builds hurt reproducibility?They depend on a tool being present at a specific version on the machine's PATH; different developers/CI agents can get different results. You mitigate by pinning/vendoring the tool or using a plugin that downloads it.
saying these in an interview costs you the question
- Claiming exec:java forks a new process
- Saying antrun needs a separate Ant install (it bundles Ant)
- Treating exec:exec as reproducible regardless of host tooling
- Recommending antrun for large procedural logic