Ant Network and Remote Tasks

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 covers the Ant tasks that open a network connection: get (HTTP, HTTPS and other URL schemes), setproxy, the three SSH tasks sshexec, scp and sshsession, ftp, mail and sql. It is written against Apache Ant 1.10.18 and was checked on 2026-10-16 with JDK 26 on macOS. Network work is where a build file most easily leaks a secret or trusts the wrong server, so the page also covers the 1.10.x security changes, including CVE-2026-78254, which is fixed in 1.10.18.

How the examples were run. There is no remote server in this environment and none was contacted. Every example that produced output below ran against a throwaway server on localhost that was started for this page: a small Python HTTP server (with a redirect and a Basic-authentication route) and a fake HTTP proxy for get and setproxy, a minimal passive-mode FTP server, an SMTP sink that prints what it receives, and an embedded H2 database for sql. These servers are test aids and are not shown. The password used in the examples is the placeholder local-test-only, passed on the command line (-Dftp.password=…); no real host, user or secret appears. The SSH tasks need a real SSH daemon, and starting one would mean opening a login path to this machine, so they were not run here beyond the failure cases noted in their section. FTPS and TLS mail were also not run here. Absolute paths are shortened to ., the clean and prepare target lines are left out of the listings, and nothing else is edited. All tasks named here ship in Ant’s optional JARs; the libraries they depend on do not, which is the first thing to get right.

Libraries that these tasks need

Ant’s lib directory contains ant-commons-net.jar, ant-jsch.jar, ant-jakartamail.jar and ant-javamail.jar, which hold the task classes. The third-party library each task calls is not bundled. Without it Ant reports a task that "cannot be created", as the second row of the next table shows.

Task Needs Notes

get, setproxy

nothing extra

Uses the JDK’s HttpURLConnection.

ftp

Apache Commons Net (commons-net)

Minimum 1.4.0, but the manual says not to use 3.2 because of performance problems. In this run commons-net 3.13.0 also needed commons-io 2.21.0 on the class path; without it the task failed with NoClassDefFoundError: org/apache/commons/io/IOUtils.

sshexec, scp, sshsession

JSch (com.jcraft:jsch)

Ant’s manual says 0.1.55 or later. Without it: Problem: failed to create task or type sshexec … Could not load a dependent class com/jcraft/jsch/Logger.

mail

Jakarta Mail and Jakarta Activation (or the older javax.mail)

Since Ant 1.10.13 both are supported and jakarta.mail is preferred when both are present. Without them the task falls back to plain text (see below).

sql

A JDBC driver

Given with classpath, classpathref or a nested classpath; "Class Not Found: JDBC driver … could not be loaded" otherwise.

The two ways to supply the JARs are -lib path on the command line (used for the runs below) and placing them in ANT_HOME/lib or ~/.ant/lib. The choice and the trade-offs are discussed on Installation; resolving the JARs with Ivy or the Maven Resolver tasks instead of vendoring them is covered on Ivy Ant Tasks and Maven Resolver Ant Tasks.

get

get downloads from any URL scheme the JVM supports: http:, https:, ftp: and jar:. It shows one . per 100 KB when verbose is on. The attributes you will meet:

Attribute or element Effect

src, nested resource collection (for example url)

One URL, or a set of them. With more than one, dest must be a directory (it is created if missing). The target name is the last part of the URL path unless a nested mapper says otherwise.

dest

File or directory to write to. Required.

usetimestamp

Download only if the remote file is newer than the local copy. HTTP only. The downloaded file gets the remote timestamp.

skipexisting (Ant 1.8.0)

Skip files that already exist, whatever their age.

retries (Ant 1.8.0)

Attempts to open the URL, default 3. The name is misleading: 1 means "do not retry" and 0 means "do not even try".

maxtime (Ant 1.8.0)

Seconds a single download may take before it is interrupted and treated as an error. 0 (the default) means unlimited.

ignoreerrors, quiet, verbose

Log errors without failing; log errors only; show progress dots.

username, password

HTTP Basic authentication. Only safe over HTTPS.

authenticateOnRedirect (Ant 1.10.13)

Send the credentials to the redirect target too. Default false.

useragent (Ant 1.9.3), nested header

The User-Agent and any other request headers.

tryGzipEncoding (Ant 1.9.5)

Ask for gzip and decompress transparently. It also means .tar.gz files are unpacked automatically.

httpusecaches

A hint to the connection about caching; implementations may ignore it.

A plain download

The target downloads the station list from the local server and prints its first two lines:

build.xml (get-basic target)
<target name="get-basic" depends="prepare">
  <get src="${server}/stations.csv" dest="${build.dir}/dl/stations.csv"/>
  <loadfile srcfile="${build.dir}/dl/stations.csv" property="csv">
    <filterchain><headfilter lines="2"/></filterchain>
  </loadfile>
  <echo>${csv}</echo>
</target>
get-basic:
      [get] Getting: http://localhost:8099/stations.csv
      [get] To: ./build/dl/stations.csv
     [echo] id,name,latitude,longitude,constituents
     [echo] SF,San Francisco,37.8063,-122.4659,M2:0.55:120;S2:0.13:150;K1:0.38:210

BUILD SUCCESSFUL
Total time: 0 seconds

Source: Ant manual, get

The destination directory must exist

When dest is a file, get does not create its parent directory. The same task, without the mkdir that the prepare target performs, fails with a Java exception and no hint about the cause:

BUILD FAILED
./build.xml:15: java.nio.file.NoSuchFileException: ./build/dl/stations.csv

Add <mkdir dir="…"/> before get. (When several URLs are given and dest is a directory, Ant creates it.)

Only download what changed

usetimestamp="true" sends the local file’s time to the server and keeps the existing copy when the server says it is not newer. Running the task twice shows both outcomes:

build.xml (get-timestamp target)
<target name="get-timestamp" depends="prepare">
  <get src="${server}/stations.csv" dest="${build.dir}/dl/stations.csv" usetimestamp="true" verbose="true"/>
  <get src="${server}/stations.csv" dest="${build.dir}/dl/stations.csv" usetimestamp="true"/>
</target>
get-timestamp:
      [get] Getting: http://localhost:8099/stations.csv
      [get] To: ./build/dl/stations.csv
      [get] .
      [get] last modified = Fri Oct 16 11:38:34 CEST 2026
      [get] Getting: http://localhost:8099/stations.csv
      [get] To: ./build/dl/stations.csv
      [get] Not modified - so not downloaded

BUILD SUCCESSFUL

The facility only works over HTTP. For other schemes use skipexisting, or compare with uptodate (Conditions & Control Flow).

Source: Ant manual, get

Failure, retries and ignoreerrors

When the server cannot be reached the build fails with the connection error; retries bounds the attempts (the refused connection is not retried forever):

get-down:
      [get] Getting: http://localhost:8098/stations.csv
      [get] To: ./build/dl/stations.csv
      [get] Error getting http://localhost:8098/stations.csv to ./build/dl/stations.csv

BUILD FAILED
./build.xml:32: java.net.ConnectException: Connection refused

With ignoreerrors="true" the same download logs the error and the build continues, which is right for an optional download (a cache warm-up) and wrong for anything the build needs:

get-ignore:
      [get] Getting: http://localhost:8098/stations.csv
      [get] To: ./build/dl/stations.csv
      [get] Error getting http://localhost:8098/stations.csv to ./build/dl/stations.csv
     [echo] build continues

BUILD SUCCESSFUL

Credentials and redirects

Ant 1.10.13 added authenticateOnRedirect, because credentials sent along a redirect can reach a different host than the one the user trusted. The default is false, so builds that relied on credentials being re-sent after a redirect may break, as the release notes warn. The test server answers /moved-private with a redirect to /private.csv, which needs Basic authentication. The server’s own log is the evidence of what was sent:

build.xml (get-auth-redirect and get-auth-redirect-on targets)
<target name="get-auth-redirect" depends="prepare">
  <get src="${server}/moved-private" dest="${build.dir}/dl/private.csv"
       username="tidepool" password="${demo.password}" retries="1"/>
</target>

<target name="get-auth-redirect-on" depends="prepare">
  <get src="${server}/moved-private" dest="${build.dir}/dl/private.csv"
       username="tidepool" password="${demo.password}" authenticateOnRedirect="true"/>
  <echo>downloaded ${build.dir}/dl/private.csv</echo>
</target>

Run as ant get-auth-redirect -Ddemo.password=local-test-only. The default fails after the redirect:

get-auth-redirect:
      [get] Getting: http://localhost:8099/moved-private
      [get] To: ./build/dl/private.csv
      [get] http://localhost:8099/moved-private moved to /private.csv

BUILD FAILED
./build.xml:42: HTTP Authorization failure

What the server saw, request by request (auth=yes means an Authorization header arrived):

GET /moved-private auth=yes
GET /private.csv auth=no

With authenticateOnRedirect="true" the second request carries the header and the download succeeds:

GET /moved-private auth=yes
GET /private.csv auth=yes

Keep the password out of the build file: pass it with -D, read it from an environment variable with <property environment="env"/> and ${env.NAME}, or ask for it with input (see Utility Tasks). The manual states plainly that Basic authentication sends the password as plain text and is only secure over HTTPS. Use authenticateOnRedirect="true" only when you control every host the redirect can lead to.

Source: Ant manual, get; Ant 1.10.18 WHATSNEW (the 1.10.13 entry)

Several files, headers and a mapper

Nested url elements select several downloads, header adds request headers, and a mapper renames the results. The firstmatchmapper below renames only the redirecting URL and flattens the rest to their last path segment:

build.xml (get-multi target)
<target name="get-multi" depends="clean">
  <get dest="${build.dir}/dl">
    <url url="${server}/stations.csv"/>
    <url url="${server}/moved"/>
    <header name="X-Build" value="tidepool-docs"/>
    <firstmatchmapper>
      <globmapper from="${server}/moved" to="moved.csv"/>
      <flattenmapper/>
    </firstmatchmapper>
  </get>
  <fileset id="dl" dir="${build.dir}/dl"/>
  <pathconvert property="dl.list" refid="dl" pathsep=", " dirsep="/">
    <map from="${build.dir}/dl/" to=""/>
  </pathconvert>
  <echo>downloaded: ${dl.list}</echo>
</target>
     [echo] downloaded: moved.csv, stations.csv

BUILD SUCCESSFUL

The manual’s example get-and-checksum macro downloads an artifact and its .sha1 file together and verifies the checksum with checksum; the same idea is recommended for any file whose integrity matters (Supply Chain Security).

setproxy and proxies

setproxy sets the JVM’s HTTP, FTP and SOCKS proxy system properties, so tasks that run inside the same JVM (such as get) go through the proxy. Its attributes are proxyhost, proxyport (default 80), proxyuser, proxypassword, nonproxyhosts (separated by |), socksproxyhost and socksproxyport (default 1080). An empty proxyhost or socksproxyhost turns that proxy off. Ant 1.7 and later can also use the operating system’s proxy settings with the -autoproxy command-line option (sql uses them by default, and -noproxy switches that off). The manual warns that with proxies on, a get against localhost may be relayed to the proxy.

To show the effect without a real proxy, the target points setproxy at a fake proxy on 127.0.0.1:8097 that records what it receives, with nonproxyhosts emptied so that no host bypasses it, and requests a URL on the unresolvable name tidepool.invalid:

build.xml (proxy target)
<target name="proxy" depends="prepare">
  <setproxy proxyhost="127.0.0.1" proxyport="8097" nonproxyhosts=""/>
  <get src="http://tidepool.invalid/stations.csv" dest="${build.dir}/dl/proxied.csv"/>
  <setproxy proxyhost=""/>
  <echo>done</echo>
</target>
proxy:
 [setproxy] Setting proxy to 127.0.0.1:8097
      [get] Getting: http://tidepool.invalid/stations.csv
      [get] To: ./build/dl/proxied.csv
 [setproxy] resetting http proxy
     [echo] done

BUILD SUCCESSFUL

The fake proxy’s own log shows that the request reached it in absolute form, so the JVM did not try to resolve tidepool.invalid itself:

PROXY saw: GET http://tidepool.invalid/stations.csv  Host: tidepool.invalid

Two limits from the manual: the ftp task does not use the proxy set by setproxy and cannot go through a SOCKS firewall, and setproxy changes JVM-wide state, so reset it when you are done, as the target does.

ftp

ftp is a basic FTP client with the action values put (the default, also spelled send), get (recv), del (delete), list, chmod, mkdir, rmdir and site. It needs server, userid and password; port defaults to 21. Files are chosen with nested filesets: for put the fileset is evaluated locally, for get, del, list and chmod it is evaluated against the remote files under remotedir. The most useful attributes:

Attribute Effect

remotedir

The remote base directory. It must already exist: Ant does not create it for put (see below).

passive

Use passive mode. Try it first if you can connect but not transfer; it is what works through most firewalls.

binary

Binary (default) or text transfers.

depends (also newer)

Transfer only new or changed files. Time-zone and granularity problems are handled with timestampGranularity, serverTimeZoneConfig and timediffauto; timediffmillis is deprecated.

retriesAllowed, skipFailedTransfers, ignoreNoncriticalErrors

How hard to try before giving up.

preservelastmodified

Keep the remote timestamp on downloaded files.

umask, chmod

Unix permissions for uploads.

systemTypeKey and the …Config attributes

Describe a server whose directory listing format cannot be auto-detected.

useFtps (Ant 1.10.13), useSecureDataChannel

Use FTPS, and the PROT P secure data channel. Since Ant 1.10.18 useSecureDataChannel defaults to true (it defaulted to false in 1.10.15 to 1.10.17); it is ignored when FTPS is off.

enableRemoteVerification (Ant 1.8.0)

Check that the data connection goes to the same host as the control connection. On by default; leave it on.

dataTimeout (Ant 1.10.7)

Milliseconds to wait for data.

allowFilesToEscapeDest (Ant 1.10.18)

Allow received files to land outside the destination directory. Default false. See the CVE section.

Make the directory, then put, list and get

Run against the throwaway FTP server on port 2121 with -lib pointing at commons-net and commons-io. The target ftp-mkdir creates the remote directory (a put into a missing directory fails, the first listing below), ftp-put uploads, ftp-list writes a listing to a file, and ftp-get downloads into a local directory:

build.xml (ftp targets)
<target name="ftp-mkdir" depends="prepare">
  <ftp action="mkdir" server="localhost" port="2121" userid="tidepool" password="${ftp.password}"
       remotedir="incoming" passive="yes"/>
</target>

<target name="ftp-put" depends="ftp-mkdir">
  <ftp server="localhost" port="2121" userid="tidepool" password="${ftp.password}"
       remotedir="incoming" passive="yes" verbose="yes" depends="yes">
    <fileset dir="data" includes="*.csv"/>
  </ftp>
</target>

<target name="ftp-list" depends="ftp-put">
  <ftp action="list" server="localhost" port="2121" userid="tidepool" password="${ftp.password}"
       remotedir="incoming" passive="yes" listing="${build.dir}/remote-listing.txt">
    <fileset dir="."><include name="*.csv"/></fileset>
  </ftp>
  <loadfile srcfile="${build.dir}/remote-listing.txt" property="listing"/>
  <echo>${listing}</echo>
</target>

<target name="ftp-get" depends="ftp-put">
  <ftp action="get" server="localhost" port="2121" userid="tidepool" password="${ftp.password}"
       remotedir="incoming" passive="yes" verbose="yes" preservelastmodified="yes">
    <fileset dir="${build.dir}/dl"><include name="*.csv"/></fileset>
  </ftp>
  <echo>fetched into ${build.dir}/dl</echo>
</target>

A put into a directory that does not exist fails at once; the remote server answered 550 to CWD incoming:

BUILD FAILED
./build.xml:128: could not change remote directory: 550 no such directory

With the directory made, ant -Dftp.password=local-test-only ftp-list ran the upload and the listing, and ftp-get the download (path prefixes shortened):

ftp-put:
      [ftp] sending files
      [ftp] transferring ./data/stations.csv
      [ftp] 1 files sent

ftp-list:
      [ftp] listing files
      [ftp] 1 files listed
     [echo] -rw-r--r-- 1 ftp ftp      239 Oct 16 09:40 stations.csv

ftp-get:
      [ftp] getting files
      [ftp] transferring stations.csv to ./build/dl/stations.csv
      [ftp] 1 files retrieved
     [echo] fetched into ./build/dl

A wrong password stops the build with a short message (the server’s 530 is only in its own log):

BUILD FAILED
./build.xml:157: Could not login to FTP server

FTPS (useFtps="true") needs a server with TLS and was not run here. Plain FTP sends the password and the data in clear text and, as the security page notes, a machine in the middle can substitute files; prefer FTPS or SFTP, or do not use it at all.

sshexec, scp and sshsession

The three SSH tasks share a set of connection attributes: host, username, port (default 22), password or keyfile (with passphrase), trust, knownhosts and failonerror. Each task also accepts sshConfig (Ant 1.10.8) to read the user name and key from an OpenSSH config file, and a nested additionalConfig (Ant 1.10.10) for JSch session options Ant has no attribute for.

  • sshexec runs command (or the commands in commandResource) on the remote host. Output can go to a file (output, errorOutput, append), to properties (outputproperty, errorproperty, resultproperty), or be suppressed. timeout is in milliseconds (unlike telnet, which uses seconds). usepty allocates a pseudo-terminal, serverAliveIntervalSeconds keeps idle connections alive, and the hideSensitive, bindSensitive, sensitiveDelimiter and placeholderBrackets attributes of Ant 1.10.16 mask secrets in the log without hiding the whole output.

  • scp copies a file or a fileset to or from a remote machine. A remote path has the form user[:password]@host:/dir, and the task decides local versus remote by looking for an @; use localFile, remoteFile, localTodir, remoteTodir and related attributes when a local path contains one. sftp="true" uses the SFTP protocol (recommended for servers without SCP1 support); preserveLastModified, filemode and dirmode (Ant 1.9.5) and compressed (Ant 1.9.8) are also available. A fileset can only be uploaded, not downloaded.

  • sshsession opens one connection, sets up any number of local tunnels (localtunnels, or nested localtunnel with lport, rhost, rport) and remote tunnels (remotetunnels, or remotetunnel), and runs nested tasks inside a sequential element while the connection is up.

Host keys: trust and knownhosts

With the default trust="false" the host must already be listed in the knownhosts file (default ${user.home}/.ssh/known_hosts, SSH2 format only), and the file must exist. trust="true" accepts any unknown host, which removes the protection against a machine in the middle and should not appear in a build that handles secrets. Keep a pinned known_hosts file in the repository or on the CI agent and point knownhosts at it.

Example (not run here)

The following pair of tasks deploys the tidepool-cli distribution and unpacks it. The user name, key and host are placeholders taken from properties; none is a real value. The tasks were not run here because they need an SSH daemon:

<scp todir="${deploy.user}@${deploy.host}:/srv/tidepool" keyfile="${deploy.key}" passphrase="${deploy.key.passphrase}"
     sftp="true" knownhosts="${basedir}/ssh/known_hosts">
  <fileset dir="${dist.dir}" includes="tidepool-cli-*.zip"/>
</scp>

<sshexec host="${deploy.host}" username="${deploy.user}" keyfile="${deploy.key}" passphrase="${deploy.key.passphrase}"
         knownhosts="${basedir}/ssh/known_hosts" command="cd /srv/tidepool &amp;&amp; unzip -o tidepool-cli-*.zip"
         timeout="60000" resultproperty="deploy.exit" failonerror="false"/>

What was run is the failure behaviour. With JSch 0.1.55 on the class path and a throwaway RSA key generated for the test, both tasks aimed at a closed port on localhost fail while connecting:

ssh-refused:
  [sshexec] Connecting to localhost:2299

BUILD FAILED
./build.xml:167: com.jcraft.jsch.JSchException: java.net.ConnectException: Connection refused
scp-refused:
      [scp] Connecting to localhost:2299

BUILD FAILED
./build.xml:172: com.jcraft.jsch.JSchException: java.net.ConnectException: Connection refused

Without the JSch JAR the same sshexec cannot even be created:

ssh-nojsch:

BUILD FAILED
./build.xml:162: Problem: failed to create task or type sshexec
Cause: Could not load a dependent class com/jcraft/jsch/Logger
       It is not enough to have Ant's optional JARs
       you need the JAR files that the optional tasks depend upon.

The Ant manual requires JSch 0.1.55 or later, so check which JSch artifact your organisation allows before adopting these tasks.

mail

mail sends an SMTP message. mailhost defaults to localhost and mailport to 25. Recipients go in tolist, cclist and bcclist, or in nested to, cc, bcc elements; addresses may be of the form name <addr> (the angle brackets must be XML entities). The body comes from message, a nested message element or messagefile. Attachments are files or a nested attachments path. Other attributes: subject, from, replyto, encoding (mime, uu, plain or auto), charset, failonerror, ignoreInvalidRecipients.

SMTP authentication (user, password) and TLS/SSL (ssl, enableStartTLS, requireStartTLS, checkServerIdentity) need Jakarta Mail and MIME encoding. Two security changes matter:

  • Ant 1.10.16 made the mail task and MailLogger verify the server identity (RFC 2595) on TLS connections; set checkServerIdentity="false" only for a test server.

  • Ant 1.10.16 also added requireStartTLS. Without it a server that does not offer STARTTLS can silently push the exchange back to unencrypted authentication.

The fallback when Jakarta Mail is missing

With only ant.jar and no mail libraries on the class path, Ant tries MIME mail (Jakarta, then javax), then UU mail, and finally sends plain text with the attachment pasted into the body. The target sends a message with data/stations.csv attached to the SMTP sink on port 2525:

build.xml (mail target)
<target name="mail">
  <tstamp/>
  <mail mailhost="localhost" mailport="2525" subject="tidepool build ${DSTAMP}"
        from="build@tidepool.invalid" tolist="team@tidepool.invalid" files="data/stations.csv">
    <message>The nightly build finished.</message>
  </mail>
</target>
mail:
     [mail] Could not find Jakarta MIME mail: jakarta.activation.DataHandler
     [mail] Could not find MIME mail: javax.activation.DataHandler
     [mail] Failed to initialise MIME mail: Abstract class org.apache.tools.ant.taskdefs.email.Mailer
     [mail] Failed to initialise UU mail: class org.apache.tools.ant.util.ClasspathUtils cannot access a member of class org.apache.tools.ant.taskdefs.email.UUMailer with package access
     [mail] Sending email: tidepool build 20261016
     [mail] Sent email with 1 attachment

The message is delivered, but the sink shows that the "attachment" is the file’s text appended to the body (plain encoding, Content-Type: text/plain). The UU line is noise on a modern JDK. With Jakarta Mail (jakarta.mail-api 2.1.5 and the Angus Mail implementation 2.0.5) and Jakarta Activation (jakarta.activation-api 2.1.4 and Angus Activation 2.0.3) added with -lib, the output is quiet and the sink receives a real multipart/mixed message:

mail:
     [mail] Sending email: tidepool build 20261016
     [mail] Sent email with 1 attachment

The part headers the sink printed for the attachment: Content-Type: application/octet-stream; name=stations.csv and Content-Disposition: attachment; filename=stations.csv.

Requiring STARTTLS

The SMTP sink does not offer STARTTLS. With requireStartTLS="true" the build refuses to talk to it:

mail-starttls:
     [mail] Sending email: tls
     [mail] Failed to send email: STARTTLS is required but host does not support STARTTLS

BUILD FAILED
./build.xml:116: Problem while sending mime mail:

And with nothing listening on the port:

mail-down:
     [mail] Sending email: x
     [mail] Failed to send email: Couldn't connect to host, port: localhost, 2526; timeout -1

BUILD FAILED
./build.xml:121: Problem while sending mime mail:

TLS and SMTP authentication against a real mail server were not run here. Never put a mail password in the build file; use the same property or environment techniques as for get.

Source: Ant manual, mail; Ant 1.10.18 WHATSNEW (1.10.16 entries)

sql

sql runs SQL through JDBC. The required attributes are driver, url, userid and password; the statements come from src, from a nested resource collection (each resource runs in its own transaction), from nested transaction elements, or from the task body. Statements are separated by ; by default (delimiter and delimitertype change that: row requires the delimiter on a line by itself, which stored-procedure scripts need, usually with keepformat). Lines starting with --, // or REM are comments.

The behaviour switches to know: autocommit (default false, so the statements run as one transaction), onerror (abort, the default, continue or stop), print, showheaders, showtrailers, output and append for result sets, csvColumnSeparator and csvQuoteCharacter, errorproperty, warningproperty and rowcountproperty, and failOnConnectionError="false" to continue when the database is unreachable. expandproperties is on by default, so a property reference such as ${name} inside the SQL is replaced by Ant before the database sees it; that is a convenience and an injection risk if the property is user-supplied. connectionProperty passes extra JDBC properties.

Load the station list and query it

The example uses H2 2.4.240 from the local Maven repository as a file database (org.h2.Driver). The first sql runs sql/init.sql, which loads data/stations.csv with H2’s CSVREAD; the second prints a result set to a file:

sql/init.sql
-- load the station list that the other examples download
DROP TABLE IF EXISTS station;
CREATE TABLE station AS SELECT id, name, latitude, longitude FROM CSVREAD('data/stations.csv');
build.xml (sql target)
<path id="jdbc.path">
  <pathelement location="${user.home}/.m2/repository/com/h2database/h2/2.4.240/h2-2.4.240.jar"/>
</path>

<target name="sql" depends="prepare">
  <sql driver="org.h2.Driver" url="jdbc:h2:file:${build.dir}/tidepool-db"
       userid="sa" password="" classpathref="jdbc.path" src="sql/init.sql"/>
  <sql driver="org.h2.Driver" url="jdbc:h2:file:${build.dir}/tidepool-db"
       userid="sa" password="" classpathref="jdbc.path"
       print="true" output="${build.dir}/stations-report.txt">
    SELECT id, name FROM station ORDER BY id;
  </sql>
  <loadfile srcfile="${build.dir}/stations-report.txt" property="report"/>
  <echo>${report}</echo>
</target>
sql:
      [sql] Executing resource: ./sql/init.sql
      [sql] 2 of 2 SQL statements executed successfully
      [sql] Executing commands
      [sql] 1 of 1 SQL statements executed successfully
     [echo] ID,NAME
     [echo] BOS,Boston
     [echo] HNL,Honolulu
     [echo] SF,San Francisco
     [echo]
     [echo] 0 rows affected

The empty password is H2’s default for a file database that was just created; use a property for a real database.

Errors: continue, stop and abort

With onerror="continue" a failing statement is reported and the following ones still run, and errorproperty records that something failed. The run below (with -v for the per-statement lines) executed a good query, a query on a missing column and a count:

sql-error:
      [sql] connecting to jdbc:h2:file:./build/tidepool-db
      [sql] Loading org.h2.Driver using a cached AntClassLoader.
      [sql] Executing commands
      [sql] SQL:  SELECT id FROM station WHERE id = 'SF'
      [sql] ID
      [sql] SF
      [sql] SQL:  SELECT nope FROM station
      [sql] Failed to execute:  SELECT nope FROM station
      [sql] org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "NOPE" not found; SQL statement:
      [sql] SELECT nope FROM station [42122-240]
      [sql] SQL:  SELECT COUNT(*) AS n FROM station
      [sql] N
      [sql] 3
      [sql] 2 of 3 SQL statements executed successfully
     [echo] sql.failed=true

(Blank lines and the "0 rows affected" trailers are left out of this listing.) Without the driver on the class path, the task reports the cause at once:

./build.xml:103: Class Not Found: JDBC driver org.h2.Driver could not be loaded

Keep JDBC drivers and credentials out of ANT_HOME/lib; give them to the task with classpath or classpathref, where Ant caches the loader (caching="true", the default).

Sources: Ant manual, sql

CVE-2026-78254: path traversal in ftp and scp

The Apache Ant security page lists Moderate: Path traversal in ftp and scp tasks allows arbitrary file write, CVE-2026-78254, fixed in Ant 1.10.18. The ftp and scp tasks can download files from a remote server, and a malicious server could return relative paths that make the task write outside the target directory, overwriting files with the permissions of the user running Ant. Exploiting it needs a malicious server or a machine in the middle. For scp and for ftp over FTPS the server must also pass the identity checks; for plain ftp a machine in the middle can supply malicious files. The report was received on 20 August 2026 and made public on 6 September 2026. The advisory sits under "Fixed in Apache Ant 1.10.18", while its "Affects" line reads "all version with ftp or scp tasks up to 1.10.18" (sic); the manual documents allowFilesToEscapeDest as new in 1.10.18, so use 1.10.18 or later and re-read the page when a newer release appears.

The fix is the allowFilesToEscapeDest attribute, default false, on ftp (for get) and scp (for downloads). The test FTP server was started with a listing that also returned the name ../escape.csv. On Ant 1.10.18 the task skips that entry:

build.xml (ftp-get-evil target)
<target name="ftp-get-evil" depends="ftp-put">
  <ftp action="get" server="localhost" port="2122" userid="tidepool" password="${ftp.password}"
       remotedir="incoming" passive="yes" verbose="yes">
    <fileset dir="${build.dir}/dl"><include name="**"/></fileset>
  </ftp>
  <available file="${build.dir}/escape.csv" property="escaped"/>
  <echo>file written outside build/dl: ${escaped}</echo>
</target>
ftp-get-evil:
      [ftp] getting files
      [ftp] skipping ../escape.csv as its target ./build/escape.csv is outside of ./build/dl.
      [ftp] transferring stations.csv to ./build/dl/stations.csv
      [ftp] 1 files retrieved
      [ftp] 1 files were not successfully retrieved
     [echo] file written outside build/dl: ${escaped}

${escaped} stayed unset, so nothing was written to build/escape.csv. Note that the build still ended with BUILD SUCCESSFUL: the skipped file produced a log line and a count, not a failure, so a build that must treat it as an error has to check for it. What to do: upgrade to 1.10.18 or later; leave allowFilesToEscapeDest at false; use scp or ftp only with servers you control and with host-key or TLS identity checks on.

Equivalents in Maven and Gradle

  • Download a file: Maven has no download goal in the core; the maven-dependency-plugin can fetch artifacts, and a build usually declares what it needs as a dependency instead. Gradle declares dependencies too. See Dependency Management Fundamentals.

  • Deploy over SSH or FTP: Maven reaches non-HTTP repositories through Wagon providers (see Install, Deploy & Distribution Management); Gradle has no built-in SSH task, and a build that needs one usually calls Ant’s tasks through Ant integration.

  • Proxy settings: Maven uses settings.xml proxy blocks (settings.xml & Password Encryption); Gradle uses gradle.properties proxy properties. Ant uses setproxy or -autoproxy.

  • Run SQL: Maven has the sql-maven-plugin from Mojo; Gradle has no SQL task in the core and uses plugins or the Ant sql task.

Where the books and older build files are outdated

  • Examples that pass username and password straight into get or ftp predate any discussion of leaking secrets in a build file or in the log; use properties and keep logs free of them.

  • Pre-1.10.13 get examples that rely on credentials being re-sent after a redirect stop working unless you set authenticateOnRedirect="true".

  • javax.mail setups still work, but Jakarta Mail is preferred from Ant 1.10.13 and the jar and package names changed.

  • The JSch requirement moved: older text says 0.1.28 or 0.1.29; the manual now says 0.1.55 or later.

  • The timediffmillis attribute of ftp is deprecated; use timestampGranularity and serverTimeZoneConfig.

References