Ant Deprecations & Migration

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, released on 6 September 2026, and was checked on 2026-10-16 on JDK 26 (and, where it says so, on JDK 17) on macOS. It is the upgrade guide of the Ant section: what an old build file needs when it is moved from Ant 1.9 to 1.10, which tasks are deprecated, which have stopped working because the JDK removed what they wrapped, what has no maintained replacement, and what to do when the better answer is to leave Ant. Every example was run in a scratch directory with the Ant 1.10.18 distribution; the output is shown as Ant printed it, with the scratch directory shortened to .. Where something could not be run (a task that needs a third-party library or an old server), the text says "not run here".

Ant’s own policy explains the shape of this page. The project’s task design guidelines open with "Don’t break existing builds": a change that breaks a build file in the wild is a bug, so tasks are not deleted because they are old. A task disappears only when the thing it wraps has disappeared from the JDK or from the world, and an old task that still works prints a DEPRECATED warning for years instead. That is why most of the list below is a list of warnings, and why the short list of real removals follows the JDK, not Ant. The release-by-release history, with dates, is on Versions & Release History; this page turns it into actions. The running scenario is the tidepool toolkit described on the section landing page.

Upgrading from Ant 1.9 to 1.10

Ant 1.10.0 was branched from 1.9.7 and has one rule that overrides all others: it needs Java 8 or later to run. The 1.9 line stayed on Java 5 to the end and reached end of life on 19 June 2024, so a build that still runs on 1.9 is on an unmaintained Ant. For the other lines the Ant FAQ and installation manual give the same table, kept here in short form:

Ant line Java needed to run it Notes

1.10.x

8 or later

Current. The more recent the JDK, the more tasks work, but each JDK removes something (below).

1.9.x

5 or later

End of life since 19 June 2024; last release 1.9.16.

1.8.x, 1.7.x, 1.6.x

1.4, 1.3, 1.2

Historical.

The Java version needed to run Ant is not the Java version a build targets: javac can compile for an older release with release, as shown on Compiling Java. A project that runs Ant 1.10.18 on JDK 26 and compiles for Java 17 is a normal setup, and it is the one this page used.

The list below holds what the Ant WHATSNEW file calls "changes that could break older environments", and the other entries that a 1.9 build is likely to hit. The release column says when the change arrived; for the dates see the versions page.

Release Change What to do

1.10.0

Java 8 minimum. The apt task is removed (the apt tool itself went away with Java 8). A fileset, zipfileset or tarfileset with both dir and file now fails the build. ant.java.version is 9 on Java 9, not 1.9. rmic refuses the -Xnew compiler on Java 9. javah defaults to the forking implementation.

Drop apt; give a fileset either dir or file; compare ant.java.version as a number, not as 1.x.

1.10.2

delete no longer accepts a wildcard in file. The Log4jListener and the image task are deprecated.

Use a fileset inside delete. Drop log4j 1.x. Use imageio.

1.10.3

copy skips, rather than fails on, a resource with a null name. junitlauncher appears (JUnit 5).

Nothing to change; see the testing page.

1.10.4

unzip, unjar and untar refuse entries that would be written outside the destination (a "zip slip"); stripAbsolutePathSpec is true by default.

Set allowFilesToEscapeDest="true" only for archives you trust and need that for.

1.10.6

image stops working on Java 9 and later; imageio replaces it. The classes behind junitlauncher were renamed.

Use imageio. Only Java code that referenced the class names is affected.

1.10.13

get has authenticateOnRedirect, false by default, so credentials are no longer sent to a redirect target.

Set it to true only if a trusted redirect needs the credentials.

1.10.14

On Java 18 and later Ant no longer uses a SecurityManager: the permissions type does not work, and a System.exit() in a task that is not forked ends the whole Ant process.

Fork (fork="true") anything that may call System.exit().

1.10.16

xslt compares timestamps with the common granularity logic (new granularity attribute). mail and MailLogger check the server identity with TLS. Windows junctions are treated as symbolic links.

granularity="0" restores the old xslt behaviour; use checkServerIdentity="false" only on purpose; on Windows, a build that used followSymlinks="false" and relied on junctions being followed needs followSymlinks="true" plus the symlink selector to exclude real symbolic links.

1.10.18

The ftp and scp tasks have allowFilesToEscapeDest, false by default, and ftp has useSecureDataChannel, true by default. SOURCE_DATE_EPOCH makes tstamp use UTC and en_US.

Set the attribute explicitly if a download really must write outside its target; expect tstamp values to change when SOURCE_DATE_EPOCH is set.

The ftp and scp items for 1.10.18 are in the WHATSNEW file in the Apache GitHub repository, under "Changes that could break older environments". The WHATSNEW inside the 1.10.18 binary archive that was downloaded for this page does not have that block (its 1.10.18 section lists only the tstamp, propertyfile, record and jakarta.mail items), so the archive’s copy is not enough on its own. Read the GitHub copy and the security page when upgrading to 1.10.18. The ant.apache.org/WHATSNEW address itself is a 404 at the time of writing. The GitHub copy also holds a section for a 1.10.19 that is not released yet (a BCEL update); when it ships, this list will need a line.

Two of these can be seen directly. ant.java.version is the plain feature number (26, not 1.26); comparing it as a number is easier than parsing it, and the javaversion condition (see Conditions & Control Flow) does the comparison for you:

$ ant -q -f script.xml props
     [echo] ant.version=Apache Ant(TM) version 1.10.18 compiled on September 3 2026  ant.java.version=26  java.version=26
BUILD SUCCESSFUL
Total time: 0 seconds

(The same build file printed ant.java.version=17 and java.version=17.0.14 on JDK 17.) And the wildcard in delete is now an inert literal name: with a.bak and b.bak in stage, <delete file="stage/*.bak"/> succeeded and deleted nothing, whereas the fileset form removes them:

removed.xml (excerpt)
<target name="wildcard-delete">
  <delete file="stage/*.bak"/>
</target>
<target name="fileset-delete">
  <delete verbose="true">
    <fileset dir="stage" includes="*.bak"/>
  </delete>
</target>
$ ls stage
a.bak  b.bak  data  stations.csv
$ ant -f removed.xml wildcard-delete
Buildfile: ./removed.xml

wildcard-delete:

BUILD SUCCESSFUL
Total time: 0 seconds
$ ls stage
a.bak  b.bak  data  stations.csv
$ ant -f removed.xml fileset-delete
Buildfile: ./removed.xml

fileset-delete:
   [delete] Deleting ./stage/a.bak
   [delete] Deleting ./stage/b.bak

BUILD SUCCESSFUL
Total time: 0 seconds
$ ls stage
data  stations.csv

A silent no-op is the dangerous kind of change. Build files that relied on the wildcard and are moved to 1.10 do not fail; they stop cleaning. Search old build files for file=" with an asterisk after the upgrade.

Deprecated tasks and what replaces them

The Ant manual marks these tasks "deprecated". They still run; copydir, copyfile, deltree, rename and renameext also print a warning in every run, as the log below shows.

Task Replacement Notes

copydir, copyfile

copy

Deprecated since Ant 1.2.

deltree

delete

Deprecated since Ant 1.2.

rename

move

Deprecated since Ant 1.2.

renameext (documented on the renameextensions page)

move with a glob mapper

The warning prints the replacement.

mimemail

mail

mimemail needs Jakarta Mail and Jakarta Activation as well. Not run here (it needs a mail server).

jlink

jar or zip with a nested zipfileset or zipgroupfileset

Not the JDK’s jlink tool; that is the separate link task (Ant 1.10.6).

image

imageio

Deprecated in 1.10.2 and broken on Java 9 and later.

jspc

The JSP compiler of the servlet container

The manual says the task was only tested with Tomcat 4.x and recommends compiling with the container’s own task, or deploying the JSPs and testing them; see Running Programs for the Ant java task that would call it.

style (as a name)

xslt

The task is the same; the manual says style is a deprecated name. The manual page for xslt is Tasks/style.html.

Log4jListener

Another listener

Deprecated in 1.10.2 because log4j 1.x is unmaintained.

The first group is the one most old build files contain. This build uses all five on the tidepool data directory:

old.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool-old" default="stage" basedir=".">
  <target name="clean">
    <deltree dir="stage"/>
  </target>

  <target name="stage" depends="clean">
    <mkdir dir="stage/data"/>
    <copydir src="data" dest="stage/data"/>
    <copyfile src="data/stations.csv" dest="stage/stations-copy.csv"/>
    <rename src="stage/stations-copy.csv" dest="stage/stations.csv"/>
    <touch file="stage/a.tmp"/>
    <touch file="stage/b.tmp"/>
    <renameext srcDir="stage" fromExtension=".tmp" toExtension=".bak"/>
  </target>
</project>
ant -f old.xml
Buildfile: ./old.xml

clean:
  [deltree] DEPRECATED - The deltree task is deprecated.  Use delete instead.

stage:
    [mkdir] Created dir: ./stage/data
  [copydir] DEPRECATED - The copydir task is deprecated.  Use copy instead.
  [copydir] Copying 1 file to ./stage/data
 [copyfile] DEPRECATED - The copyfile task is deprecated.  Use copy instead.
   [rename] DEPRECATED - The rename task is deprecated.  Use move instead.
    [touch] Creating ./stage/a.tmp
    [touch] Creating ./stage/b.tmp
[renameext] DEPRECATED - The renameext task is deprecated.  Use move instead.
[renameext] Replace this with:
[renameext] <move todir="./stage" overwrite="false">
[renameext]   <fileset dir="./stage" />
[renameext]   <mapper type="glob"
[renameext]           from="*.tmp"
[renameext]           to="*.bak" />
[renameext] </move>
[renameext] using the same patterns on <fileset> as you've used here
[renameext] Moving 2 files to ./stage

BUILD SUCCESSFUL
Total time: 0 seconds

Two details. The task name in the build file is renameext, while the manual page is called renameextensions; the build file with the long name would fail with an undefined-task error. And renameext prints a ready-to-paste replacement, which the others do not. The migrated version is shorter and has no warnings:

new.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool-new" default="stage" basedir=".">
  <target name="clean">
    <delete dir="stage"/>
  </target>

  <target name="stage" depends="clean">
    <mkdir dir="stage/data"/>
    <copy todir="stage/data">
      <fileset dir="data"/>
    </copy>
    <copy file="data/stations.csv" tofile="stage/stations.csv"/>
    <touch file="stage/a.tmp"/>
    <touch file="stage/b.tmp"/>
    <move todir="stage">
      <fileset dir="stage" includes="*.tmp"/>
      <mapper type="glob" from="*.tmp" to="*.bak"/>
    </move>
  </target>
</project>
Buildfile: ./new.xml

clean:
   [delete] Deleting directory ./stage

stage:
    [mkdir] Created dir: ./stage/data
     [copy] Copying 1 file to ./stage/data
     [copy] Copying 1 file to ./stage
    [touch] Creating ./stage/a.tmp
    [touch] Creating ./stage/b.tmp
     [move] Moving 2 files to ./stage

BUILD SUCCESSFUL
Total time: 0 seconds

Both builds left a.bak, b.bak, data and stations.csv in stage. (The copy task also replaced the two-step copyfile plus rename, because tofile names the target directly.) The Ant distribution ships etc/ stylesheets for checking and rewriting build files, as the installation manual says, but the list in 1.10.18 contains only the reporting stylesheets (junit-frames.xsl, log.xsl and similar); there is no deprecation migrator among them, so the table above is the checklist. All of these tasks are described on File Operations.

Tasks that no longer work

A task can stop working without Ant changing, because the JDK removed what it wrapped. Each failure below was produced on JDK 26; the ones marked also ran on JDK 17.

Task Breaks on Use instead

javah

Java 10 (removed from the JDK; deprecated in 9). Also JDK 17.

javac with nativeheaderdir (Java 8 and later).

rmic

Java 15 (removed; deprecated in 13). Also JDK 17.

Dynamic proxies; or set the executable attribute to the rmic of an older JDK.

apt

Removed from Ant in 1.10.0 (the tool left Java 8).

Annotation processing in javac.

image

Java 9 (Java Advanced Imaging internals).

imageio.

script with language="javascript"

Java 15 (the Nashorn engine was removed).

Add a JSR 223 engine, such as GraalVM JavaScript, to Ant’s classpath; or use another language.

permissions, in-process System.exit()

Java 18 (the SecurityManager is disallowed by default).

Fork the java task.

<delete file="*.x">

Ant 1.10.2 (see above).

A fileset.

javah and rmic

<target name="javah" depends="compile">
  <javah class="io.tidepool.Native" destdir="build/headers" classpath="build/classes"/>
</target>

<target name="javac-native-headers">
  <delete dir="build/classes"/>
  <delete dir="build/headers"/>
  <mkdir dir="build/classes"/>
  <mkdir dir="build/headers"/>
  <javac srcdir="src" destdir="build/classes" nativeheaderdir="build/headers"
         release="17" includeantruntime="false"/>
</target>

<target name="rmic" depends="compile">
  <rmic base="build/classes" includes="io/tidepool/Native.class"/>
</target>

(The class Native declares one native method, ticks().)

$ ant -f removed.xml javah
Buildfile: ./removed.xml

compile:

javah:

BUILD FAILED
./removed.xml:9: javah does not exist under Java 10 and higher, use the javac task with nativeHeaderDir instead

$ ant -f removed.xml javac-native-headers
…
    [javac] Compiling 2 source files to ./build/classes
BUILD SUCCESSFUL

$ ls build/headers
io_tidepool_Native.h

$ ant -f removed.xml rmic
…
BUILD FAILED
./removed.xml:22: rmic does not exist under Java 15 and higher, use rmic of an older JDK and explicitly set the executable attribute

The replacement for javah is a single attribute on a task the build already has, and the generated header contains the JNI declaration for ticks. For rmic there is no JDK replacement: Java RMI has not needed generated stubs since Java 5, so the usual migration is to delete the task. On JDK 17 the rmic build printed RMI Compiling 1 class to … and then the same failure message. Both messages are Ant’s own, not the JDK’s, and name the fix.

script and JavaScript

script with language="javascript" was a standard way to write a bit of logic in a build file when Java shipped Nashorn. Java 15 removed it (JEP 372), and Ant says so in its error:

script.xml (excerpt)
<target name="js">
  <script language="javascript"><![CDATA[
    project.setProperty("greeting", "hello from javascript");
  ]]></script>
  <echo>${greeting}</echo>
</target>
$ ant -f script.xml js
Buildfile: ./script.xml

js:
Java 15 has removed Nashorn, you must provide an engine for running JavaScript yourself. GraalVM JavaScript currently is the preferred option.

BUILD FAILED
./script.xml:4: Unable to create javax script engine for javascript

Ant 1.10.9 added support for GraalVM JavaScript; the engine and its dependencies have to be put on Ant’s classpath (with -lib or in ANT_HOME/lib), because Ant does not ship them. Putting one on the classpath was not done here. See Scripting for the engines and the alternatives (a macrodef, a custom task), which are usually a better answer than an extra runtime.

permissions and System.exit()

The permissions type used a Java SecurityManager to sandbox an in-process java task. Java 17 deprecated the SecurityManager for removal (JEP 411) and Java 18 disallows it by default, so from Ant 1.10.14 the type does nothing on those JDKs. The first run below tries it; the second is the other consequence, a task that calls System.exit(3) in Ant’s own JVM:

script.xml and removed.xml (excerpts)
<target name="perm" depends="compile">
  <java classname="io.tidepool.Exiter" classpath="build/classes" fork="false">
    <permissions>
      <grant class="java.lang.RuntimePermission" name="exitVM"/>
    </permissions>
  </java>
  <echo>after</echo>
</target>

<target name="exit-unforked" depends="compile">
  <java classname="io.tidepool.Exiter" classpath="build/classes" fork="false"/>
  <echo>still alive after System.exit</echo>
</target>

<target name="exit-forked" depends="compile">
  <java classname="io.tidepool.Exiter" classpath="build/classes" fork="true"
        resultproperty="rc" failonerror="false"/>
  <echo>still alive, exit code ${rc}</echo>
</target>

(Exiter.main prints about to call System.exit(3) and calls it.)

$ ant -f script.xml perm
…
perm:
     [java] Use of <permissions> or org.apache.tools.ant.types.Permissions is disallowed in current Java runtime version
     [java] 	at org.apache.tools.ant.types.Permissions.setSecurityManager(Permissions.java:122)
     [java] 	at org.apache.tools.ant.taskdefs.ExecuteJava.run(ExecuteJava.java:217)
     …
     [java] Java Result: -1
     [echo] after
BUILD SUCCESSFUL

$ ant -f removed.xml exit-unforked
Buildfile: ./removed.xml

compile:

exit-unforked:
     [java] about to call System.exit(3)
$ echo $?
3

$ ant -f removed.xml exit-forked
…
     [java] Java Result: 3
     [echo] still alive, exit code 3
BUILD SUCCESSFUL

The in-process exit killed Ant: no BUILD SUCCESSFUL or BUILD FAILED line, no still alive message, and Ant’s own exit code was the program’s. The permissions run is worse in a quieter way: the program was not run and the build still reported success (the stack trace is printed, Java Result: -1 is logged, and failonerror was not set). Both are fixed the same way: fork="true", which gives the program its own JVM and lets Ant read the exit code. The task is described on Running Programs.

New JDKs, old compiler flags

Not an Ant change, but the first thing a JDK upgrade shows in an Ant log. The source and target attributes of javac become obsolete as the JDK moves on, and the release attribute is the supported replacement. These three targets compile the same sources on JDK 26:

javac.xml (excerpt)
<javac srcdir="src" destdir="build/c1" source="8" target="8" includeantruntime="false"/>
<javac srcdir="src" destdir="build/c2" release="8" includeantruntime="false"/>
<javac srcdir="src" destdir="build/c3" release="17" includeantruntime="false"/>
### source="8" target="8"
    [javac] Compiling 2 source files to ./build/c1
    [javac] warning: [options] bootstrap class path is not set in conjunction with -source 8
    [javac]   not setting the bootstrap class path may lead to class files that cannot run on JDK 8
    [javac]     --release 8 is recommended instead of -source 8 -target 8 because it sets the bootstrap class path automatically
    [javac] warning: [options] source value 8 is obsolete and will be removed in a future release
    [javac] warning: [options] target value 8 is obsolete and will be removed in a future release
    [javac] warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
    [javac] 4 warnings
### release="8"
    [javac] Compiling 2 source files to ./build/c2
    [javac] warning: [options] source value 8 is obsolete and will be removed in a future release
    [javac] warning: [options] target value 8 is obsolete and will be removed in a future release
    [javac] warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
    [javac] 3 warnings
### release="17"
    [javac] Compiling 2 source files to ./build/c3

Three lessons. source/target also leave the bootstrap class path alone, so the code may use JDK 26 methods that do not exist on Java 8 and still compile (the release form prevents that). The warnings are JDK warnings; they do not change the exit code. And the cure for the noise is to raise the target to a supported release, not to silence the lint. The javac task is the subject of Compiling Java.

Tasks that survive but are legacy

These tasks are in the 1.10.18 manual, work as far as their external tool does, and are not deprecated. Most of them wrap products that few teams still run, so the practical question is not "what replaces the task?" but "should this build still talk to that product?". None was run here: they need the external tool or server.

Tasks Status

cvs, cvspass, cvstagdiff, changelog

CVS client tasks. They need the cvs binary on the path. Git and Subversion are not covered by Ant’s core; call the client with exec, or let the CI system do the checkout.

vss*, sos*, clearcase (cc*)

Microsoft Visual SourceSafe, SourceOffSite and ClearCase. Each needs the vendor’s command-line client. Treat as end-of-life products.

ejbjar, blgenclient, iplanet-ejbc, serverdeploy

The EJB tasks. The manual says development of these tasks is effectively frozen and that EJB 3.0 made the whole process obsolete. The application servers they list are from the early 2000s.

netrexxc, javacc, jjtree, antlr

Compiler-generator tasks that need their libraries. antlr targets the ANTLR 2 generation; current ANTLR versions are used through their own tools or a Maven or Gradle plugin.

.NET tasks (csc, ilasm, ildasm, dotnet), Perforce tasks, Visual Age for Java, Testlet, Metamata, Sitraka

Already gone: the .NET tasks were deprecated in Ant 1.7.0 and removed in 1.8.0 (the stand-alone .NET Antlib took over), the Perforce tasks were removed in 1.9.0 (replaced by the vendor’s own), and the others in 1.7.0 because their libraries no longer exist. Old build files that name them fail with "task undefined".

The EJB manual page, the cvs page and the vss page are the pages to cite when deciding. For a build that must keep talking to such a system, the way to keep the tasks is to leave the library and the vendor’s client where they are; for one that does not, deleting the target is the migration.

junit: not deprecated, but not for JUnit 5

The junit task is not deprecated in 1.10.18. Its manual page says it was tested with JUnit 3.0 to 3.8.2 and also works with JUnit 4.x, and it needs junit.jar and ant-junit.jar on Ant’s classpath. For JUnit 5 and later, the junitlauncher task (added in 1.10.3) runs tests through the JUnit Platform, including JUnit 4 tests through the Vintage engine. A build that adopts JUnit 5 therefore replaces junit with junitlauncher; one that stays on JUnit 4 can keep junit. Both are covered with runnable examples on Testing with JUnit & junitlauncher.

Replacing Ant-Contrib

Old build files often declare the Ant-Contrib task library for <if>, <foreach>, <for> and a few property tasks. The Ant site lists Ant-Contrib on its projects page, but it is dormant and not hosted by Apache, and a new build should not add it. Most of what it provided exists in Ant itself, some of it since Ant 1.9.1 or earlier. The loops are explained, with runnable examples, on Conditions & Control Flow; this build shows the two commonest replacements side by side with the Ant-Contrib line they replace (the Ant-Contrib lines are in comments; the library was not downloaded):

contrib.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="contrib-replacements" default="all" basedir="."
         xmlns:if="ant:if" xmlns:unless="ant:unless">
  <!-- Ant-Contrib:  <if><equals arg1="${env}" arg2="prod"/><then>...</then><else>...</else></if> -->
  <target name="conditional">
    <condition property="is.prod">
      <equals arg1="${env}" arg2="prod"/>
    </condition>
    <echo if:set="is.prod">production settings</echo>
    <echo unless:set="is.prod">development settings</echo>
  </target>

  <!-- Ant-Contrib:  <foreach list="SF,BOS,HNL" param="id" target="report"/> -->
  <macrodef name="report-station">
    <attribute name="id"/>
    <sequential>
      <echo>station @{id}</echo>
    </sequential>
  </macrodef>
  <target name="loop">
    <report-station id="SF"/>
    <report-station id="BOS"/>
    <report-station id="HNL"/>
  </target>

  <target name="all" depends="conditional,loop"/>
</project>
$ ant -q -f contrib.xml conditional
     [echo] development settings
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant -q -Denv=prod -f contrib.xml conditional
     [echo] production settings
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant -q -f contrib.xml loop
     [echo] station SF
     [echo] station BOS
     [echo] station HNL
BUILD SUCCESSFUL
Total time: 0 seconds
Ant-Contrib Closest core Ant

<if>, <then>, <else>

<condition> and the if: and unless: attributes on any task (Ant 1.9.1), or if and unless on targets

<foreach>, <for>

macrodef with @{} attributes, subant over a dirset or filelist, apply, or a mapper

<var>, <propertycopy>

No direct equivalent: Ant properties are immutable by design. <local> (Ant 1.8) gives a scoped property, and a macrodef attribute carries a value into a block; usually the build should be restructured instead

<trycatch>, <throw>

No direct equivalent. <fail> with a condition, <retry>, and failonerror="false" with resultproperty cover the common cases

<outofdate>

<uptodate> and <dependset>

Build-file style that ages well

Ant has no build-file checker beyond its own parser, so the checklist is what the project’s own documents teach. Two of them are old and still worth reading, with a caveat about their age: Ant in Anger was last updated on 16 March 2005, and its advice (define standard targets, keep a clean directory layout, do not make the build a scripting language) is sound, but its examples predate the packaging, dependency and Java changes above. The task design guidelines are current and say what the project promises about compatibility. For a 1.10.18 build file the practical rules are:

  • Use release, not source and target, in javac, and set includeantruntime="false".

  • Replace deprecated task names (above) and wildcard delete calls.

  • Fork anything that may call System.exit(); do not use permissions.

  • Put third-party task libraries behind taskdef with an explicit classpath, or in -lib, not in ANT_HOME/lib (see Installation).

  • Keep dependencies out of the repository with Ivy (Ivy Overview) or the Maven Resolver Ant Tasks.

  • Prefer import and macrodef to copy-pasted targets (Multi-File Builds and Macros & Presets).

  • After every JDK or Ant upgrade, run the build once and read every DEPRECATED and warning: line; they are the changes above arriving one at a time.

Moving to Maven or Gradle

Sometimes the right answer to a pile of deprecations is another tool. The question of whether to move is on Comparing Ant, Maven & Gradle; the how, with a complete tidepool migration in each direction and checks that the results match, is on Migrating Between Tools (Ant to Maven, Ant to Gradle, and Ivy to the Resolver Ant Tasks). This section adds the part that belongs to Ant: both tools can run an existing Ant build as it is, so the migration can start before the first target is rewritten. The two examples use the legacy old.xml above (renamed build.xml) and its deprecated tasks.

Gradle: ant.importBuild

Gradle embeds Ant. ant.importBuild turns every target of a build file into a Gradle task, and the optional closure renames them. The Gradle manual describes this under "Ant integration" and in its Ant migration guide.

Kotlin DSL
// Every Ant target of build.xml becomes a Gradle task, renamed with an "ant-" prefix.
ant.importBuild("build.xml") { antTargetName ->
    "ant-$antTargetName"
}

tasks.register("stageData") {
    group = "tidepool"
    description = "Stages the data files through the imported Ant build"
    dependsOn("ant-stage")
}
Groovy DSL
// Every Ant target of build.xml becomes a Gradle task, renamed with an "ant-" prefix.
ant.importBuild('build.xml') { antTargetName ->
    "ant-${antTargetName}".toString()
}

tasks.register('stageData') {
    group = 'tidepool'
    description = 'Stages the data files through the imported Ant build'
    dependsOn 'ant-stage'
}

Both versions were run with the Gradle 9.8.1 distribution (gradle; with a wrapper the command is ./gradlew), with settings.gradle.kts or settings.gradle holding only rootProject.name.

gradle --console=plain stageData
> Task :ant-clean

> Task :ant-stage
[ant:renameext] DEPRECATED - The renameext task is deprecated.  Use move instead.

> Task :stageData

BUILD SUCCESSFUL in 259ms
2 actionable tasks: 2 executed

Gradle shows Ant’s output at its own log levels, so at the default level only the warning-level line is shown (Gradle’s ant-clean task printed nothing, although deltree also warned). With --info all the messages of the original run appear, with the prefix [ant:<task>]:

> Task :ant-clean
[ant:deltree] DEPRECATED - The deltree task is deprecated.  Use delete instead.
[ant:deltree] Deleting: ./stage
> Task :ant-stage
[ant:mkdir] Created dir: ./stage/data
[ant:copydir] DEPRECATED - The copydir task is deprecated.  Use copy instead.
[ant:copydir] Copying 1 file to ./stage/data
…
[ant:renameext] Moving 2 files to ./stage

The task list also shows the imported targets under "Other tasks" (ant-clean, ant-stage). The Groovy build produced the same output. Gradle bundles its own Ant: gradle --version on 9.8.1 reports Ant 1.10.17, which is not the Ant installed on the machine (1.10.18), so Ant features newer than the bundled one are unavailable, and extra Ant task libraries have to be added to Gradle’s ant classpath. The step-by-step version of this migration, replacing one imported target at a time and moving the Ivy dependencies, is on Migrating Between Tools; the Gradle side is described on Gradle: Ant Integration.

Maven: maven-antrun-plugin

Maven has no importBuild, but maven-antrun-plugin runs Ant tasks, including the ant task that calls a target of an existing build file, from a phase of the lifecycle. The pom below (a pom-packaging project, since there is nothing to compile) calls the stage target of the legacy build during process-resources:

pom.xml
<?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>io.tidepool</groupId>
  <artifactId>tidepool-ant-bridge</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>3.2.0</version>
        <executions>
          <execution>
            <id>stage-data</id>
            <phase>process-resources</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <echo>Running inside Ant ${ant.version}</echo>
                <ant antfile="${project.basedir}/build.xml" dir="${project.basedir}" target="stage"/>
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
mvn -B -ntp process-resources
[INFO] --- antrun:3.2.0:run (stage-data) @ tidepool-ant-bridge ---
[INFO] Executing tasks
[WARNING]      [echo] Running inside Ant Apache Ant(TM) version 1.10.15 compiled on August 25 2024
[INFO]   [deltree] DEPRECATED - The deltree task is deprecated.  Use delete instead.
[INFO]     [mkdir] Created dir: ./stage/data
[INFO]   [copydir] DEPRECATED - The copydir task is deprecated.  Use copy instead.
[INFO]   [copydir] Copying 1 file to ./stage/data
[INFO]  [copyfile] DEPRECATED - The copyfile task is deprecated.  Use copy instead.
[INFO]    [rename] DEPRECATED - The rename task is deprecated.  Use move instead.
[INFO]     [touch] Creating ./stage/a.tmp
[INFO]     [touch] Creating ./stage/b.tmp
[WARNING] [renameext] DEPRECATED - The renameext task is deprecated.  Use move instead.
[INFO] [renameext] Replace this with:
…
[INFO] [renameext] Moving 2 files to ./stage
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

(Run with Maven 3.10.0 on JDK 26.) The same files ended up in stage. Notice the line Running inside Ant …: the plugin ran Ant 1.10.15, the version the plugin depends on, so across the three runs of this page three different Ants executed the same build file, 1.10.18 (standalone), 1.10.17 (Gradle 9.8.1) and 1.10.15 (maven-antrun-plugin 3.2.0). That is fine for core tasks, and a surprise for anything newer. Extra Ant libraries (JSch, the JUnit launcher) have to be added as dependencies of the plugin. Maven’s side of the bridge, including this plugin’s configuration, is on Plugins & Their Configuration.

Which way

  • Move the dependencies first if the pain is the lib/ directory: Ivy or the Maven Resolver Ant Tasks change nothing else.

  • Go to Maven when the project is a library or an application that fits the standard layout and lifecycle, and the team wants conventions and a plugin ecosystem; maven-antrun-plugin carries what has not yet been rewritten.

  • Go to Gradle when the build has many custom steps, several modules or a need for incremental builds and caching; the importBuild path lets the Ant targets be replaced one by one.

  • Stay on Ant when the build is small, stable and understood. Ant 1.10.18 is maintained, runs on Java 8 and later, and a few warnings in the log are not a reason to rewrite.

Where the books are outdated

The Ant books on the reading list were written for Ant 1.6 to 1.8, so their advice needs these corrections for 1.10.18. Specific book pages are not quoted here:

  • They use copydir, copyfile, deltree and rename and javac with source and target where the manual now recommends copy, delete, move and release.

  • They use javah, rmic, apt and the image task as live examples; none works on a current JDK.

  • They embed JavaScript in script on the assumption that the JDK has an engine; that ended with Java 15.

  • They teach the junit task as the way to test; for JUnit 5 it is junitlauncher.

  • They use Ant-Contrib for control flow; core Ant has the replacements above.

  • Their dependency chapters stop at a lib/ directory or Ivy; the Maven Resolver Ant Tasks postdate them.

  • Their advice on build-file style (the appendix on style, the task guidelines, "Ant in Anger") is mostly still right, and its Java and tooling assumptions are not.