Running Programs

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Ant 1.10.18, and every example was run with it on JDK 26 on macOS on 2026-10-16. The examples are small complete build files for the tidepool scenario used across this section. Each one runs with ant -f <file> <target> from the project directory, and the Java class they start is a stand-in for the tidepool command-line tool whose source is shown below. The console output is real, with three kinds of lines left out: the Buildfile: line, blank lines and the Total time: line. The project directory is shortened to /work/tidepool.

A build runs other programs for many reasons: to start the program it has just built (a smoke test of the command-line tool), to run a code generator or a Java-based tool that has no Ant task, to call a native command such as git or tar, and to run a tool once for each of many files. Ant has three tasks for this, one for each need.

Task Runs Typical use

java

a Java class, a JAR, a module or a single source file, in Ant’s JVM or in a new one

a smoke test of the application, a code generator written in Java

exec

one native command

git describe, tar, a shell script

apply

a native command over the files of a resource collection, once per file or in batches

an image converter, a checksum tool

The redirector type, which connects the standard input, output and error of the program to files, properties and filter chains, is shared by all three.

The program used in the examples

The Java stand-in for the tidepool tool prints its arguments, and its first argument selects a behaviour: info prints the system property tidepool.units, the maximum heap size, the working directory and whether assertions are on; exit <n> writes a message to standard error and calls System.exit; sleep <ms> waits; stdin echoes its input.

src/org/tidepool/Main.java
package org.tidepool;

import java.nio.file.Files;
import java.nio.file.Path;

/** A tiny stand-in for the tidepool CLI, used to exercise Ant's execution tasks. */
public final class Main {
    private Main() { }

    public static void main(String[] args) throws Exception {
        System.out.println("args=" + String.join(",", args));
        if (args.length == 0) {
            return;
        }
        switch (args[0]) {
            case "info" -> {
                boolean assertions = false;
                assert assertions = true;
                System.out.println("tidepool.units=" + System.getProperty("tidepool.units", "<unset>"));
                System.out.println("maxMemory=" + Runtime.getRuntime().maxMemory() / (1024 * 1024) + "M");
                System.out.println("cwd=" + Path.of("").toAbsolutePath().getFileName());
                System.out.println("assertions=" + assertions);
            }
            case "exit" -> {
                System.err.println("exiting with status " + args[1]);
                System.exit(Integer.parseInt(args[1]));
            }
            case "sleep" -> Thread.sleep(Long.parseLong(args[1]));
            case "stdin" -> System.out.println("stdin=" + new String(System.in.readAllBytes()).trim());
            case "stations" -> System.out.println("stations=" + (Files.readAllLines(Path.of(args[1])).size() - 1));
            default -> { }
        }
    }
}

The files in this section are this page’s own setup for the examples that follow, not taken from the documentation.

Running Java with java

The java task runs a Java class "within the running (Apache Ant) JVM or forks another JVM if specified". Exactly one of four attributes says what to run.

Attribute Runs Requires

classname

the main method of a class found on the classpath

nothing; may be combined with module

jar

the Main-Class of a JAR

fork="true"; the classpath is ignored, as the java -jar command ignores it

module

the main module (its Main-Class, or the class named by classname) resolved on the modulepath; since 1.9.7

fork="true"

sourcefile

a .java file, or a file with a shebang line, launched by the Java 11 single-file source launcher; since 1.10.5

fork="true"

In the Ant JVM and in a forked JVM

Without fork, the class runs inside Ant’s own JVM, with Ant’s JVM settings, in Ant’s class loader hierarchy and in the working directory in which Ant was started. That is cheap, since there is no process to start, but it has drawbacks: JVM options such as -Xmx cannot be set, a program that calls System.exit ends Ant itself, a static variable survives into the next task, and assertions are ignored. fork="true" runs a new JVM that starts a fresh copy of the program: JVM arguments (jvmarg, maxmemory), a different working directory (dir), a different Java installation (jvm), an input stream, a timeout and a clean exit status are all available there. The ant manual’s advice is "if odd things go wrong when you run this task, set fork = true".

Attribute Meaning Default

classname, jar, module, sourcefile

what to run, as above

none (exactly one of classname, jar, module, sourcefile)

classpath, classpathref

the class path, as an attribute or by reference; a nested <classpath> is also accepted

Ant’s class path

modulepath, modulepathref

the module path; nested <modulepath> and <upgrademodulepath> are also accepted

none

fork

run the class in a new JVM

false

spawn

start a process that outlives Ant; needs fork="true" and cannot be combined with timeout, input, output, error or result

false

jvm

the command that starts the forked JVM

java

jvmargs, args

deprecated attributes: use nested <jvmarg> and <arg> elements

none

maxmemory

the maximum heap of the forked JVM (128m)

JVM default

dir

the working directory of the forked JVM

the project’s basedir

failonerror

fail the build if the exit status is not 0

false (see below)

resultproperty

a property to receive the exit status; useful with failonerror="false" and fork="true"

none

timeout

kill the program after this many milliseconds; use it only with fork="true"

none

output, error, append, outputproperty, errorproperty, logError

where standard output and standard error go; see the redirector below

the Ant log

input, inputstring

standard input from a file or a string; mutually exclusive

the console, unless spawn="true"

newenvironment

do not pass Ant’s environment variables to the JVM when <env> is used

false

clonevm

give the forked JVM the system properties and the boot class path of Ant’s JVM

false

discardOutput, discardError

throw standard output or error away (since 1.10.10)

false

The nested elements are <arg> and <jvmarg> for the program’s and the JVM’s command line, <sysproperty> and <syspropertyset> for system properties, <classpath>, <bootclasspath>, <modulepath>, <upgrademodulepath>, <env> (environment variables for a forked JVM; the description is under exec below), <assertions>, <permissions> and <redirector>.

The first build runs the class in Ant’s JVM and then forks it. Two points to see in the output: <arg value="station list"/> is one argument that contains a space, while <arg line="--units metric"/> is split into two; and the forked JVM has its own heap limit and working directory, with assertions enabled by the -ea option.

run-java.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-java" default="compile" basedir=".">
  <target name="compile">
    <mkdir dir="build/classes"/>
    <javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
  </target>

  <!-- 1. in the Ant JVM (fork="false", the default) -->
  <target name="in-process" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes">
      <arg value="info"/>
      <arg value="station list"/>
      <arg line="--units metric"/>
      <sysproperty key="tidepool.units" value="metric"/>
    </java>
  </target>

  <!-- 2. in a forked JVM, with JVM options and a working directory -->
  <target name="forked" depends="compile">
    <mkdir dir="build/run"/>
    <java classname="org.tidepool.Main" classpath="${basedir}/build/classes"
          fork="true" dir="build/run" maxmemory="64m">
      <jvmarg value="-Xss512k"/>
      <jvmarg value="-ea"/>
      <sysproperty key="tidepool.units" value="imperial"/>
      <arg value="info"/>
    </java>
  </target>
</project>
ant -f run-java.xml in-process
ant -f run-java.xml forked
compile:
    [mkdir] Created dir: /work/tidepool/build/classes
    [javac] Compiling 1 source file to /work/tidepool/build/classes
in-process:
     [java] args=info,station list,--units,metric
     [java] tidepool.units=metric
     [java] maxMemory=8192M
     [java] cwd=tidepool
     [java] assertions=false
BUILD SUCCESSFUL
compile:
forked:
    [mkdir] Created dir: /work/tidepool/build/run
     [java] args=info
     [java] tidepool.units=imperial
     [java] maxMemory=64M
     [java] cwd=run
     [java] assertions=true
BUILD SUCCESSFUL

The in-process run reports maxMemory=8192M, which is Ant’s heap, and cwd=tidepool, which is where Ant started. The forked run reports 64M and cwd=run, the dir that was set.

Running a JAR

jar runs the Main-Class named in the JAR’s manifest, as java -jar does, so the JVM ignores the classpath; put a Class-Path manifest attribute in the JAR instead. The jar attribute is a File, so a relative value is resolved against the project’s basedir, not against the directory the task runs in when dir is set. If you need a path relative to dir, build the full path yourself.

run-java2.xml (target jar)
  <target name="jar" depends="compile">
    <java jar="build/lib/tidepool-cli.jar" fork="true" failonerror="true">
      <arg value="info"/>
    </java>
  </target>

The complete build file, with the compile target that makes the JAR, is listed with the next example.

Exit codes, output and timeouts

By default the exit status of a forked program is ignored, which surprises people: a java task whose program fails does not fail the build. With failonerror="true" any non-zero status stops the build. resultproperty stores the status in a property, which is how to decide what to do next. The task also logs Java Result: n for a non-zero result when the build continues. A task that does not fork behaves differently: the class runs in the build’s own JVM, so there is no process exit status to inspect, and a program that calls System.exit ends Ant itself (see below).

outputproperty and errorproperty capture the streams in properties. If only outputproperty is set, error output is merged into it; here both are set, so they are kept apart. timeout kills a forked JVM that runs too long, and the task fails when failonerror is true. inputstring sends text to the standard input of the forked program.

run-java2.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-java2" default="all" basedir=".">
  <target name="compile">
    <mkdir dir="build/classes"/>
    <javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
    <jar destfile="build/lib/tidepool-cli.jar" basedir="build/classes">
      <manifest>
        <attribute name="Main-Class" value="org.tidepool.Main"/>
      </manifest>
    </jar>
  </target>

  <target name="jar" depends="compile">
    <java jar="build/lib/tidepool-cli.jar" fork="true" failonerror="true">
      <arg value="info"/>
    </java>
  </target>

  <target name="exit-codes" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true"
          resultproperty="rc" failonerror="false">
      <arg line="exit 3"/>
    </java>
    <echo message="exit status stored in rc: ${rc}"/>
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true" failonerror="true">
      <arg line="exit 3"/>
    </java>
  </target>

  <target name="capture" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true"
          outputproperty="out" errorproperty="err" resultproperty="rc2">
      <arg line="exit 2"/>
    </java>
    <echo message="rc2=${rc2}"/>
    <echo message="stdout was:${line.separator}${out}"/>
    <echo message="stderr was: ${err}"/>
  </target>

  <target name="timeout" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true"
          timeout="1500" failonerror="true">
      <arg line="sleep 10000"/>
    </java>
  </target>

  <target name="stdin" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true"
          inputstring="station BRE" outputproperty="stdin.out">
      <arg value="stdin"/>
    </java>
    <echo message="${stdin.out}"/>
  </target>

  <target name="all" depends="jar"/>
</project>
ant -f run-java2.xml exit-codes
ant -f run-java2.xml capture
ant -f run-java2.xml timeout
ant -f run-java2.xml stdin
compile:
      [jar] Building jar: /work/tidepool/build/lib/tidepool-cli.jar
jar:
     [java] args=info
     [java] tidepool.units=<unset>
     [java] maxMemory=8192M
     [java] cwd=tidepool
     [java] assertions=false
BUILD SUCCESSFUL
compile:
exit-codes:
     [java] args=exit,3
     [java] exiting with status 3
     [java] Java Result: 3
     [echo] exit status stored in rc: 3
     [java] args=exit,3
     [java] exiting with status 3
BUILD FAILED
/work/tidepool/run-java2.xml:25: Java returned: 3

The first build gave exit status 3 once with failonerror="false" (the status is stored in rc) and once with failonerror="true", which ended the build with Java returned: 3. The next builds capture streams, hit a timeout and send input:

compile:
capture:
     [java] Java Result: 2
     [echo] rc2=2
     [echo] stdout was:
     [echo] args=exit,2
     [echo] stderr was: exiting with status 2
BUILD SUCCESSFUL
compile:
timeout:
     [java] args=sleep,10000
BUILD FAILED
/work/tidepool/run-java2.xml:42: Timeout: killed the sub-process
compile:
stdin:
     [echo] args=stdin
     [echo] stdin=station BRE
BUILD SUCCESSFUL

The standard error text exiting with status 2 went to errorproperty, not into outputproperty, and the timeout produced Timeout: killed the sub-process.

Programs that call System.exit()

A Java program that calls System.exit stops the JVM it is running in. When the java task does not fork, that JVM is Ant, so the build stops in the middle of the task with no BUILD FAILED or BUILD SUCCESSFUL message and the exit status of the program becomes the exit status of Ant. Older Ant versions guarded against this by installing a Java SecurityManager that intercepted the call, which is what the <permissions> element and the default permissions did. The Java platform has removed that mechanism: JEP 411 (Java 17) deprecated the SecurityManager for removal, and JEP 486 (Java 24) permanently disabled it. Ant 1.10.14 follows: "when using Java 18 or higher, Ant will no longer use Java SecurityManager", and a task that calls System.exit() and does not run in a forked JVM "will now kill the entire Ant build process", in the words of the release notes. The remedy is to fork.

The two builds below run the same program, which exits with status 7 in the first and 0 in the second. In the first the JVM is Ant’s:

run-java3.xml (excerpt)
  <target name="exit-in-process" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" failonerror="true">
      <arg line="exit 7"/>
    </java>
    <echo message="this line is never reached"/>
  </target>

  <target name="exit-forked" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true" failonerror="true">
      <arg line="exit 0"/>
    </java>
    <echo message="the build goes on"/>
  </target>

The status.sh wrapper prints the exit status of the ant command after the build output:

compile:
exit-in-process:
     [java] args=exit,7
     [java] exiting with status 7
ant exit status: 7
compile:
exit-forked:
     [java] args=exit,0
     [java] exiting with status 0
     [echo] the build goes on
BUILD SUCCESSFUL

In the first run Ant exits with status 7, the program’s status, without printing a final message: a CI server sees a failed step but the log has no summary. The second run, forked, prints the build goes on.

The same applies to tasks that call System.exit internally, such as some code-generation or test-runner entry points invoked through java. Another consequence is that the other tasks (junit, junitlauncher) have their own fork options for the same reason; see Testing with JUnit & junitlauncher.

The permissions type on Java 18 and later

<permissions> grants and revokes Java security permissions while the class runs, through nested <grant> and <revoke> elements (class, name and actions). It requires a SecurityManager, which is why it cannot work any more: on Java 18 and later, using <permissions> throws a BuildException, and the manual recommends no longer using the type. The element is ignored when the task forks. Setting the system or Ant property ant.securitymanager.usage.warn to true replaces the exception with a logged warning, but no security checks are performed; it is a compatibility switch for old build files, not a feature.

run-permissions.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-permissions" default="restricted" basedir=".">
  <target name="compile">
    <mkdir dir="build/classes"/>
    <javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
  </target>

  <target name="restricted" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" failonerror="true">
      <permissions>
        <grant class="java.security.AllPermission"/>
        <revoke class="java.lang.RuntimePermission" name="exitVM"/>
      </permissions>
      <arg line="exit 0"/>
    </java>
    <echo message="reached after the java task"/>
  </target>
</project>
compile:
restricted:
BUILD FAILED
/work/tidepool/run-permissions.xml:9: Use of <permissions> or org.apache.tools.ant.types.Permissions is disallowed in current Java runtime version

With the property set on the command line the build continues, the security checks do nothing, and the program calls System.exit and ends Ant:

ant -f run-permissions.xml -Dant.securitymanager.usage.warn=true
compile:
restricted:
Security checks are disabled - Use of <permissions> or org.apache.tools.ant.types.Permissions is disallowed in current Java runtime version
     [java] args=exit,0
     [java] exiting with status 0

The warning is printed once; [java] args=exit,0 shows the program ran, and the reached after the java task message is missing, because the JVM was Ant’s.

Assertions

The nested <assertions> element enables or disables Java assertions for the program. <enable> and <disable> take a class or a package (a package includes its subpackages; ... means the anonymous package), and without either they apply to all user code. enableSystemAssertions controls the JDK’s own classes. The element is a data type, so it can be defined with an id and reused with refid. As the manual says, "assertion statements are currently ignored in non-forked mode", so use it with fork="true". The Main class prints assertions=true only when they are enabled.

compile:
assertions:
     [java] args=info
     [java] tidepool.units=<unset>
     [java] maxMemory=8192M
     [java] cwd=tidepool
     [java] assertions=true
BUILD SUCCESSFUL

Modules

For a modular application, module names the main module and modulepath (an attribute, or a nested <modulepath> path-like element) says where modules are found. When classname is also given, it names the main class inside the module. A modular program must be forked. The example compiles a one-class module with javac and runs it.

modsrc/module-info.java
module tidepool.hello {
    exports org.tidepool.hello;
}
run-module.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-module" default="run" basedir=".">
  <target name="compile">
    <mkdir dir="build/mods/tidepool.hello"/>
    <javac srcdir="modsrc" destdir="build/mods/tidepool.hello" release="21" includeantruntime="false"/>
  </target>

  <target name="run" depends="compile">
    <!-- module + classname: run a class of a module found on the module path -->
    <java fork="true" failonerror="true" module="tidepool.hello" classname="org.tidepool.hello.Hello">
      <modulepath>
        <pathelement location="build/mods"/>
      </modulepath>
      <arg value="Brest"/>
    </java>
  </target>

  <target name="no-fork" depends="compile">
    <java module="tidepool.hello" classname="org.tidepool.hello.Hello" modulepath="build/mods"/>
  </target>
</project>
compile:
    [mkdir] Created dir: /work/tidepool/build/mods/tidepool.hello
    [javac] Compiling 2 source files to /work/tidepool/build/mods/tidepool.hello
run:
     [java] module=tidepool.hello named=true args=Brest
BUILD SUCCESSFUL
compile:
no-fork:
BUILD FAILED
/work/tidepool/run-module.xml:19: Cannot execute a module in non-forked mode. Please set fork='true'.

The module=tidepool.hello named=true line proves the class was loaded from a named module. The second build shows the message when fork is missing. Module-aware compilation with javac is on Compiling Java.

Single-file source programs

Since Ant 1.10.5 the sourcefile attribute runs a .java file straight away through the Java 11 launcher (JEP 330), without a separate compile step. It also accepts a file with a shebang line. It forks, like jar and module, and the Java version that runs it must support the feature.

single/Hello.java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0] + " (java.version " + System.getProperty("java.specification.version") + ")");
    }
}
  <target name="sourcefile">
    <java sourcefile="single/Hello.java" fork="true" failonerror="true">
      <arg value="Brest"/>
    </java>
  </target>
sourcefile:
     [java] Hello, Brest (java.version 26)
BUILD SUCCESSFUL

System properties and clonevm

A forked JVM does not inherit the system properties of Ant’s JVM. sysproperty and syspropertyset pass the ones the program needs. clonevm="true" instead makes the forked JVM’s system properties and boot class path the same as Ant’s, which is useful when Ant is started with options such as -Dhttp.proxyHost=... through ANT_OPTS and the program should see them. The system property ant.build.clonevm does the same for every forked JVM in the build; it must be a real system property, so it cannot be set on the Ant command line (-D sets an Ant property): put it in ANT_OPTS.

The build file has two targets, one with and one without clonevm. Both run with ANT_OPTS="-Dtidepool.units=metric" in the environment:

  <target name="no-clonevm" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true">
      <arg value="info"/>
    </java>
  </target>

  <target name="clonevm" depends="compile">
    <java classname="org.tidepool.Main" classpath="build/classes" fork="true" clonevm="true">
      <arg value="info"/>
    </java>
  </target>
ANT_OPTS="-Dtidepool.units=metric" ant -f run-java3.xml no-clonevm
ANT_OPTS="-Dtidepool.units=metric" ant -f run-java3.xml clonevm

Filtered to the relevant line, the two runs print:

     [java] tidepool.units=<unset>
     [java] tidepool.units=metric

Starting a background process with spawn

spawn="true" (with fork="true") starts a process that outlives the build, for example a server that later tests talk to. The task does not wait for it, does not capture its output and cannot time it out. exec has the same attribute, and the example uses exec to show the effect. The build starts a shell that sleeps for two seconds and then writes a file; when the build checks, the file is not there yet, and it appears after Ant has ended.

run-spawn.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-spawn" default="spawn" basedir=".">
  <target name="spawn">
    <delete file="build/spawned.txt"/>
    <exec executable="sh" spawn="true">
      <arg value="-c"/>
      <arg value="sleep 2; echo finished > build/spawned.txt"/>
    </exec>
    <available file="build/spawned.txt" property="done.already"/>
    <echo message="when Ant gets here, spawned.txt exists: ${done.already}"/>
  </target>
</project>
spawn:
     [echo] when Ant gets here, spawned.txt exists: ${done.already}
BUILD SUCCESSFUL
ls: build/spawned.txt: No such file or directory
finished

When Ant itself runs as a background process on a Unix-like system (ant &), a forked java, exec or apply task that does not set spawn tries to read the terminal and suspends Ant. Give it explicit input — inputstring or input — or set spawn="true".

Running native programs with exec

exec runs one native command. Name the program in executable and give its arguments as nested <arg> elements. The command attribute, which takes the whole command line as a string, is deprecated, and Ant prints a deprecation message when it is used.

Attribute Meaning Default

executable

the program, without arguments

required (or command)

dir

the working directory

the current directory when vmlauncher is true, else the project’s basedir

os, osfamily

run only on the named operating systems (os matches the os.name property); osfamily uses the values of the os condition (windows, unix, mac…​); since 1.7 for osfamily

any

failonerror

fail the build if the exit status signals failure

false

failifexecutionfails

fail the build if the program cannot be started

true

resultproperty

a property to receive the exit status

none

timeout

kill the program after this many milliseconds; the status is then -1

none

spawn

start the program and leave it running when Ant ends

false

output, error, append, outputproperty, errorproperty, logError

redirect the streams

the Ant log

input, inputstring

standard input

EOF: a program reading its input sees end of file since Ant 1.6

newenvironment

do not pass Ant’s environment variables when <env> is used

false

vmlauncher

start the process through the JVM; if false, through the operating-system shell or the antRun script, which gives access to shell features, to scripts on Windows and to relative paths with dir

true

resolveexecutable

resolve executable against the project’s basedir and then against dir

false

searchpath

search the PATH variable when resolving executable (since 1.6.3)

false

discardOutput, discardError

throw the output or error stream away (since 1.10.10)

false

Nested elements are <arg>, <env> and <redirector>. Each <env> sets one environment variable for the child process through key and exactly one of value, path (a PATH-like value; : and ; are converted) or file (the absolute path of a file). Environment variables of the Ant process are available to the build as ${env.NAME} after <property environment="env"/>, as described on Properties.

Arguments

The nested <arg> has value (one argument, passed as is, however many spaces it holds), line (a command-line string split at spaces), file (the absolute path of a file), path (a path-like value converted to the platform’s separators) and pathref, plus prefix and suffix. The mistake the manual calls out is <arg value="-l -a"/>: it passes one argument that contains a space, and most programs reject it. Write two <arg> elements, or use line, which the manual says to avoid when possible, because Ant splits it much as a Unix shell would and the result can differ from what you expect.

run-exec.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-exec" default="exec-basic" basedir=".">
  <property environment="env"/>

  <target name="exec-basic">
    <exec executable="uname" outputproperty="os.kernel" failonerror="true">
      <arg value="-s"/>
    </exec>
    <echo message="kernel: ${os.kernel}"/>
    <!-- one argument containing spaces: use value, not line -->
    <exec executable="echo">
      <arg value="station list"/>
      <arg line="--units metric"/>
    </exec>
    <!-- environment variable for the child process only -->
    <exec executable="sh" outputproperty="env.out">
      <env key="TIDEPOOL_HOME" value="${basedir}/data"/>
      <arg value="-c"/>
      <arg value="echo home=$TIDEPOOL_HOME"/>
    </exec>
    <echo message="${env.out}"/>
  </target>

  <target name="exec-result">
    <exec executable="sh" resultproperty="rc" failonerror="false">
      <arg value="-c"/>
      <arg value="echo about to fail 1&gt;&amp;2; exit 4"/>
    </exec>
    <echo message="rc=${rc}"/>
    <exec executable="sh" failonerror="true">
      <arg value="-c"/>
      <arg value="exit 4"/>
    </exec>
  </target>

  <target name="exec-missing">
    <exec executable="no-such-program" failifexecutionfails="false" resultproperty="rc.missing"/>
    <echo message="rc.missing=${rc.missing}"/>
    <exec executable="no-such-program"/>
  </target>

  <target name="exec-os">
    <exec executable="cmd.exe" osfamily="windows" failonerror="true">
      <arg line="/c echo only on Windows"/>
    </exec>
    <exec executable="echo" osfamily="unix">
      <arg value="only on Unix"/>
    </exec>
  </target>

  <target name="exec-timeout">
    <exec executable="sleep" timeout="1500" failonerror="true">
      <arg value="30"/>
    </exec>
  </target>

  <target name="exec-input">
    <exec executable="tr" outputproperty="shout">
      <arg value="a-z"/>
      <arg value="A-Z"/>
      <redirector inputstring="brest sydney" />
    </exec>
    <echo message="${shout}"/>
    <exec executable="cat">
      <redirector outputproperty="cat.out" errorproperty="cat.err"
                  inputstring="blah before blah" alwayslog="true">
        <inputfilterchain>
          <replacestring from="before" to="after"/>
        </inputfilterchain>
      </redirector>
    </exec>
    <echo message="property cat.out = ${cat.out}"/>
  </target>
</project>
exec-basic:
     [echo] kernel: Darwin
     [exec] station list --units metric
     [echo] home=/work/tidepool/data
BUILD SUCCESSFUL

The echo line cannot show how value and line differ: both print as one line. The difference matters to the program that receives them, which gets one argument for value and one for each word of line.

Exit status, missing programs and timeouts

Like java, exec ignores the exit status unless failonerror="true". With resultproperty the status is available to the build. A command that cannot be started at all — a missing executable, a wrong directory — fails the build unless failifexecutionfails="false", in which case Ant logs the problem and carries on; the result property is then not set. The default timeout result is -1.

exec-result:
     [exec] about to fail
     [exec] Result: 4
     [echo] rc=4
BUILD FAILED
/work/tidepool/run-exec.xml:30: exec returned: 4
exec-missing:
     [exec] Execute failed: java.io.IOException: Cannot run program "no-such-program" (in directory "/work/tidepool"): Exec failed, error: 2 (No such file or directory)
     [echo] rc.missing=${rc.missing}
BUILD FAILED
/work/tidepool/run-exec.xml:39: Execute failed: java.io.IOException: Cannot run program "no-such-program" (in directory "/work/tidepool"): Exec failed, error: 2 (No such file or directory)
exec-timeout:
BUILD FAILED
/work/tidepool/run-exec.xml:52: Timeout: killed the sub-process

In the exec-missing output the Java stack trace that Ant prints for the failed start (the at ... frames and the cause) was removed. The first failing call is the one with failifexecutionfails="false": its message is logged and the build goes on, but rc.missing is not set, which is why the second echo prints it literally. The second call is the one that fails.

Restricting a command to a platform uses osfamily:

exec-os:
     [exec] only on Unix
BUILD SUCCESSFUL

Because exec does not use a shell, a build for Windows cannot run a .bat file directly; the Ant manual’s workaround is to run cmd with /c and the batch file as arguments. Running ant.bat -p that way is its example. The cmd.exe call above was skipped on macOS because of osfamily="windows", and this page did not run a Windows build.

Running a program per file with apply

apply (formerly execon, a name that still works) runs a command with the files of its resource collections added to the command line. By default it runs the command once per file; with parallel="true" it runs it once with all the files. Its attributes include everything exec has, plus the following.

Attribute Meaning Default

executable

the command, without arguments

required

parallel

one invocation with all the files, rather than one invocation per file

false

maxparallel

in parallel mode, pass at most this many files per invocation (use it when the command line gets too long)

unlimited

relative

pass names relative to the fileset’s directory, or to dest for target files

false

forwardslash

use forward slashes in names even on systems that use backslashes

false

type

file, dir or both; dirset elements are always directories

file

addsourcefile

add the source file names at the end of the command line; turn it off to place them yourself with <srcfile>

true

skipemptyfilesets

do not run the command when no file was found or all are up to date; despite the name, it also covers filelists

false

dest

the directory in which the command is expected to create target files, used with a mapper

none

force

ignore timestamps of target files (since 1.6.3)

false

ignoremissing

ignore files named in a filelist that do not exist (since 1.6.2)

true

verbose

print a summary

false

The nested elements are <fileset>, <filelist>, <dirset> and any other resource collection (since 1.7), a <mapper> for dependency checking, <arg>, <env>, <redirector> and two position markers: <srcfile> places the source file names between the arguments, and <targetfile> does the same for the target names that the mapper produces. Both accept prefix and suffix (since 1.8.0), and <targetfile> is allowed only with a mapper.

With a mapper, apply compares each source with its mapped target and skips files whose target is up to date. That makes it a small incremental build step. The example below does three things: counts the lines of each file (one invocation per file), prints all the file names in one invocation (with <srcfile/> between two arguments), and sorts each CSV file into a new file only when the target is out of date, using sh -c so the redirection works.

run-apply.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-apply" default="demo" basedir=".">
  <target name="clean">
    <delete dir="build/apply"/>
  </target>

  <target name="demo">
    <mkdir dir="build/apply"/>

    <!-- 1. one invocation per file (parallel="false", the default), file name at the end -->
    <apply executable="wc" parallel="false" relative="true" dir="tide-data">
      <arg value="-l"/>
      <fileset dir="tide-data" includes="*.csv"/>
    </apply>

    <!-- 2. one invocation for all files; place the file names with srcfile -->
    <apply executable="echo" parallel="true" dir="tide-data" relative="true">
      <arg value="csv files:"/>
      <srcfile/>
      <arg value="(end)"/>
      <fileset dir="tide-data" includes="*.csv"/>
    </apply>

    <!-- 3. dependency checking with a mapper: only files newer than their target are processed -->
    <apply executable="sh" dest="build/apply" parallel="false" verbose="true" skipemptyfilesets="true">
      <arg value="-c"/>
      <arg value="sort -t, -k2 -n -r $0 > $1"/>
      <srcfile/>
      <targetfile/>
      <fileset dir="tide-data" includes="*.csv"/>
      <mapper type="glob" from="*.csv" to="*.sorted"/>
    </apply>
  </target>
</project>

The first run creates the sorted files, and the second finds them up to date:

demo:
    [mkdir] Created dir: /work/tidepool/build/apply
    [apply]        2 brest.csv
    [apply]        1 sydney.csv
    [apply] csv files: brest.csv sydney.csv (end)
    [apply] Applied sh to 2 files and 0 directories.
BUILD SUCCESSFUL
demo:
    [apply]        2 brest.csv
    [apply]        1 sydney.csv
    [apply] csv files: brest.csv sydney.csv (end)
    [apply] Skipping fileset for directory /work/tidepool/tide-data. It is up to date.
BUILD SUCCESSFUL

In the second run wc and echo ran again, because they have no mapper, but the third apply printed Skipping fileset ... It is up to date. instead of running.

The redirector type

<redirector> is the shared way of connecting standard input, output and error. exec, apply and java accept one as a nested element. The attributes of the three tasks that have the same name — output, error, input, outputproperty, errorproperty, inputstring, append, logError — behave the same when put on the redirector. The redirector adds more.

Attribute or element Meaning Default

alwayslog

send the output to the Ant log as well as to its other destination (since 1.6.3)

false

createemptyfiles

create output and error files even when empty

true

inputencoding, outputencoding, errorencoding

character sets of the streams

the JVM default

loginputstring

log the value of inputstring; set it to false when the input is secret (since 1.6.3)

true

binaryOutput

do not split the output into lines (since 1.9.4)

false

discardOutput, discardError

throw the stream away (since 1.10.10)

false

inputmapper, outputmapper, errormapper

a mapper that chooses the file for each source file; useful with apply, where each source file gets its own output

none

inputfilterchain, outputfilterchain, errorfilterchain

a filter chain applied to the stream

none

The example sends a string to tr and captures the result, then sends a string to cat through an input filter chain that rewrites before to after, capturing the output and error in properties. alwayslog logs the output as well.

exec-input:
     [echo] BREST SYDNEY
     [exec] blah after blah
     [echo] property cat.out = blah after blah
BUILD SUCCESSFUL

The exec-input target is part of the run-exec.xml file listed above.

The same jobs in Maven and Gradle

Job Maven 3.10.0 Gradle 9.8.1

run a Java class

exec:java runs it in Maven’s JVM and exec:exec in a new process; both belong to the MojoHaus exec-maven-plugin, a third-party plugin, which has to be declared in the POM

a JavaExec task (tasks.register<JavaExec>("run")), which starts a separate JVM process

run a native command

exec:exec, or the exec task of maven-antrun-plugin

an Exec task, or the injected ExecOperations service

run a command from a build script

no equivalent; a plugin goal bound to a phase does it

Project#exec and Project#javaexec were deprecated in Gradle 8.11 and removed in 9.0; use ExecOperations or a task

run a command per file

no equivalent

a loop over a file collection in a task action that uses ExecOperations

Maven has no model for running arbitrary programs: its plugins are the programs, and the exec plugin is the usual escape hatch. Gradle has two task types for it. Ant’s java task is available from both: in Maven through maven-antrun-plugin, and in Gradle through the ant object, described on Ant Integration. The Gradle task model is on Tasks, and writing a Maven plugin that does the work in process, instead of starting a program, is on Writing Plugins.

Errors you will meet

The failing builds below come from run-errors.xml:

run-errors.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-errors" default="none" basedir=".">
  <target name="none"/>
  <target name="no-class-in-process">
    <java classname="org.tidepool.Mian" classpath="build/classes" failonerror="true"/>
  </target>
  <target name="no-class-forked">
    <java classname="org.tidepool.Mian" classpath="build/classes" fork="true" failonerror="true"/>
  </target>
  <target name="jar-no-fork">
    <java jar="build/lib/tidepool-cli.jar"/>
  </target>
  <target name="two-ways">
    <java classname="org.tidepool.Main" jar="build/lib/tidepool-cli.jar" fork="true"/>
  </target>
  <target name="neither">
    <java fork="true"/>
  </target>
  <target name="exec-two">
    <exec executable="echo" command="echo hi"/>
  </target>
  <target name="wrong-dir">
    <exec executable="echo" dir="no/such/dir"/>
  </target>
</project>
no-class-in-process:
BUILD FAILED
/work/tidepool/run-errors.xml:5: Could not find org.tidepool.Mian. Make sure you have it in your classpath
no-class-forked:
     [java] Error: Could not find or load main class org.tidepool.Mian
     [java] Caused by: java.lang.ClassNotFoundException: org.tidepool.Mian
BUILD FAILED
/work/tidepool/run-errors.xml:8: Java returned: 1
jar-no-fork:
BUILD FAILED
/work/tidepool/run-errors.xml:11: Cannot execute a jar in non-forked mode. Please set fork='true'.
two-ways:
BUILD FAILED
/work/tidepool/run-errors.xml:14: Cannot use combination of 'jar', 'sourcefile', 'classname', 'module' attributes in same command
neither:
BUILD FAILED
/work/tidepool/run-errors.xml:17: Classname must not be null.
wrong-dir:
BUILD FAILED
/work/tidepool/run-errors.xml:23: The directory /work/tidepool/no/such/dir does not exist

The exec-two target, which sets both executable and command, still works and logs The command attribute is deprecated. and Please use the executable attribute and nested arg elements.

Message Cause

Could not find org.tidepool.Mian. Make sure you have it in your classpath

a wrong class name or missing classpath in a task that does not fork

Error: Could not find or load main class ... followed by Java returned: 1

the same mistake in a forked JVM, where the message comes from the JVM

Cannot execute a jar in non-forked mode. Please set fork='true'.

jar (and module, sourcefile) without fork

Cannot use combination of 'jar', 'sourcefile', 'classname', 'module' attributes in same command

more than one of the four ways to say what to run

Classname must not be null.

none of them given

The directory ... does not exist

dir names a directory that is not there

Timeout: killed the sub-process

the program ran longer than timeout; with failonerror="false" the build goes on

Where older books are out of date

Books written for Ant 1.5, such as the 2002 Java Development with Ant, whose chapter 5 covers running programs, predate several changes:

  • They target JDK 1.3 and 1.4. Ant 1.10 needs Java 8 or later, and the manual now says that Microsoft’s jview.exe cannot be used as the jvm of a java task (it takes different parameters) and must be started from exec instead.

  • In-process programs relied on the security manager to stop System.exit. On Java 18 and later and Ant 1.10.14 and later that protection is gone, <permissions> throws, and the answer is fork="true".

  • Many attributes written in old build files, such as args and jvmargs on java and command on exec, are deprecated in favour of nested elements.

  • Modules (module, modulepath, since Ant 1.9.7), single-file programs (sourcefile, 1.10.5) and the discardOutput attributes (1.10.10) did not exist.

  • exec on Windows has not changed in substance: batch files still need cmd /c.

Where to go next