Maven Resolver Ant 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 is written against the Maven Resolver Ant Tasks 2.0.0, released on 6 August 2026, running on Apache Ant 1.10.18
with JDK 26 on macOS. Every example was run on 2026-10-16 in a scratch directory (the tidepool toolkit described on the
section landing page), and the one example that matters for the
Java floor was run again on JDK 17. Output is shown as Ant printed it; the scratch directory is shortened to . and Ant’s
own directory to $ANT_HOME. Lines of download progress that do not matter for the point being made are left out, and the
text says so each time. Maven Central and the project pages were reached on the same day.
The Maven Resolver Ant Tasks are the Apache Maven project’s answer to the question "how does an Ant build use Maven
repositories?". They put the library that Maven itself uses to find and download artifacts, Maven Resolver, behind a few Ant
tasks and types: resolve collects and downloads dependencies and turns them into paths, file sets or properties, install
copies built artifacts into a local repository, and deploy uploads them to a remote one. The same coordinates, the same
settings.xml, the same mirror and proxy rules and the same repository layout as Maven apply, so an Ant build can consume
what Maven builds publish and the other way round. The sibling page
Ivy Overview describes the other way to manage
dependencies in Ant; the table near the end of this page helps to choose between them. Dependency concepts such as scopes and
mediation are explained in
Dependency Management Fundamentals.
What the project is and what 2.0.0 changed
The tasks live in their own repository, apache/maven-resolver-ant-tasks, and are released separately from Maven and from
Maven Resolver. The releases published on Maven Central are 1.2.0, 1.2.1, 1.3.0, 1.3.1, 1.4.0, 1.5.0, 1.5.1, 1.5.2, 1.6.0,
1.6.1 and 2.0.0. The ones that matter:
| Release | Published | What changed (from the GitHub release notes) |
|---|---|---|
1.5.2 |
February 2025 |
Managed dependencies no longer default their scope to |
1.6.0 |
August 2025 |
Dependency management through the |
1.6.1 |
July 2026 |
A null pointer when |
2.0.0 |
6 August 2026 |
"Upgrade to Resolver 2 (and Maven 3.10)". This is the line the page is about. |
The POM of 2.0.0 says what the jar is built from: Maven Resolver 2.0.21, Maven 3.10.0-rc-1 for the settings and model
builders, Ant 1.10.17 as the provided Ant API, and Java 8 as the minimum (the classes have class-file version 52). The site
text still says the tasks are for "Apache Ant 1.7+". That is the declared compatibility; what was run for this page is Ant
1.10.18, on JDK 26 and on JDK 17, and both worked. Older Ant lines and older JDKs were not tried.
Getting the JAR
The artifact is org.apache.maven.resolver:maven-resolver-ant-tasks:2.0.0. Maven Central holds two JARs under it, and
choosing the right one is the first stumbling block:
| File | Size | Use |
|---|---|---|
|
4,917,168 bytes |
The one to use. It bundles Maven Resolver, the Maven model and settings builders, the Apache HTTP client, SLF4J and the other libraries the tasks need, so the build file needs this single JAR. |
|
130,194 bytes |
Only the tasks themselves. Its dependencies are listed in the POM, but Ant has no way to download them, so it is useful only to a build that already has them on a classpath (for example one assembled by Maven). |
The uber JAR is a classifier of the main artifact, so every Maven-style tool can fetch it by naming the classifier. The
dependency:get goal of the Maven Dependency Plugin does it from the command line; the local repository was redirected to
a scratch directory so that the run did not touch ~/.m2.
mvn -B -ntp -q -Dmaven.repo.local=./repo \
org.apache.maven.plugins:maven-dependency-plugin:3.11.0:get \
-Dartifact=org.apache.maven.resolver:maven-resolver-ant-tasks:2.0.0:jar:uber
ls -l repo/org/apache/maven/resolver/maven-resolver-ant-tasks/2.0.0/ | awk 'NR>1 {print $5, $9}'
316 _remote.repositories
4917168 maven-resolver-ant-tasks-2.0.0-uber.jar
32 maven-resolver-ant-tasks-2.0.0-uber.jar.md5
40 maven-resolver-ant-tasks-2.0.0-uber.jar.sha1
14667 maven-resolver-ant-tasks-2.0.0.pom
32 maven-resolver-ant-tasks-2.0.0.pom.md5
40 maven-resolver-ant-tasks-2.0.0.pom.sha1
Downloading from Central is not by itself a trust decision. The directory on Central also holds .sha1, .md5 and .asc
(PGP signature) files for every JAR. Comparing the published SHA-1 with the local one takes two lines, and both printed the
same digest:
curl -s https://repo1.maven.org/maven2/org/apache/maven/resolver/maven-resolver-ant-tasks/2.0.0/maven-resolver-ant-tasks-2.0.0-uber.jar.sha1
shasum lib/maven-resolver-ant-tasks-2.0.0-uber.jar
4ed6bce0d80a1a7fd5f25d80c1bdc5463ef137b0
4ed6bce0d80a1a7fd5f25d80c1bdc5463ef137b0 lib/maven-resolver-ant-tasks-2.0.0-uber.jar
A checksum fetched from the same server as the file only detects a damaged download. Verifying the .asc signature against
the Maven project’s published keys is the stronger check; it was not run here. The page
Supply-Chain Security explains the trade-offs, and
the Ant-side download tasks are on
Network & Remote Tasks.
Maven Central also has a CycloneDX software bill of materials for the release
(maven-resolver-ant-tasks-2.0.0-cyclonedx.json and .xml).
Making Ant load the tasks
The tasks are an antlib: a JAR with a descriptor, org/apache/maven/resolver/ant/antlib.xml, that lists the task and type
names. Antlibs are used through an XML namespace whose URI starts with antlib:; for this one it is
antlib:org.apache.maven.resolver.ant. The prefix is free; this page uses resolver:, matching the log lines Ant prints.
There are two ways to make Ant find the JAR, and both were run.
Declare it in the build file. The official page recommends copying the JAR into the project’s lib directory and loading
it with taskdef, so the build carries its own copy and needs no Ant installation changes:
<?xml version="1.0" encoding="UTF-8"?>
<project name="resolve-basic" default="resolve" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2"/>
<target name="resolve">
<resolver:resolve>
<dependencies>
<dependency coords="org.apache.commons:commons-csv:1.14.1"/>
</dependencies>
<path refid="compile.classpath" classpath="compile"/>
<files refid="lib.files" dir="build/lib" layout="{artifactId}-{version}.{extension}"/>
<properties prefix="dep."/>
</resolver:resolve>
<echo>path : ${toString:compile.classpath}</echo>
<echo>dep.org.apache.commons:commons-csv:jar = ${dep.org.apache.commons:commons-csv:jar}</echo>
</target>
</project>
Put it on Ant’s own classpath. With the JAR in -lib, in ~/.ant/lib or in $ANT_HOME/lib, the taskdef line is
unnecessary, because Ant looks up an antlib: namespace on its classpath. The same build file without the taskdef
failed without the library and worked with it:
$ ant -f resolve-basic-nodef.xml
Buildfile: ./resolve-basic-nodef.xml
BUILD FAILED
./resolve-basic-nodef.xml:5: Problem: failed to create task or type antlib:org.apache.maven.resolver.ant:localrepo
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any <presetdef>/<macrodef> declarations have taken place.
No types or tasks have been defined in this namespace yet
This appears to be an antlib declaration.
Action: Check that the implementing library exists in one of:
-$ANT_HOME/lib
-/Users/me/.ant/lib
-a directory added on the command line with the -lib argument
Total time: 0 seconds
$ ant -lib lib -f resolve-basic-nodef.xml
Buildfile: ./resolve-basic-nodef.xml
resolve:
[resolver:resolve] Resolving artifacts
[echo] path : ./build/m2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:…
[echo] dep.org.apache.commons:commons-csv:jar = ./build/m2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar
BUILD SUCCESSFUL
Total time: 0 seconds
Both -lib lib (a directory) and -lib lib/maven-resolver-ant-tasks-2.0.0-uber.jar (the file) worked. The three places
for libraries and the rule to never rely on CLASSPATH are explained on
Installation, and the mechanics of namespaces on
Antlibs & Namespaces. The taskdef form is
the better default for a shared project, because a new developer needs no setup beyond the repository; the -lib form
suits a CI image or a team that standardises its Ant installation. Two details were noticed in the first run:
-
With
ant -v, a lineCould not load definitions from resource org/apache/maven/resolver/ant/antlib.xml. It could not be found.appears next to the successful load bytaskdef. It is noise from Ant’s attempt to resolve the namespace through its own classpath first, and the build continues. -
The project’s
usage.mdwarns that loading the antlib resource does not work from Groovy’sAntBuilder; there the types and tasks have to be defined one by one with their implementation class names. That guide is the source of this statement; it was not run here.
What the antlib defines
The descriptor inside the JAR defines these names. Notice that pom is declared as a task although the site documents it as
a type, and that createPom is not described on the site’s main page at all (it is in the usage.md guide and the API
documentation):
| Kind | Names |
|---|---|
Tasks |
|
Types |
|
The nested elements of a task are matched by Ant’s introspection on the local element name, not on the namespace, so inside
a <resolver:resolve> a nested <dependencies> needs no prefix (a prefixed <resolver:dependencies> works too, and a
top-level definition outside a task needs the prefix). All examples below use the unprefixed form for nested elements.
A first resolve
The build file above asks for one dependency, commons-csv 1.14.1, with a local repository inside the project. This is the
first run, with an empty local repository, so every file is downloaded (the progress lines are shown once here):
ant -f resolve-basic.xml
Buildfile: ./resolve-basic.xml
resolve:
[resolver:resolve] Downloading from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom
[resolver:resolve] Downloaded from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom (22 KB at 648.4 KB/sec)
[resolver:resolve] Downloading from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-parent/85/commons-parent-85.pom
[resolver:resolve] Downloaded from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-parent/85/commons-parent-85.pom (77 KB at 2185.7 KB/sec)
…
[resolver:resolve] Resolving artifacts
[resolver:resolve] Downloading from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar
[resolver:resolve] Downloaded from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar (60 KB at 773.2 KB/sec)
…
[echo] path : ./build/m2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:./build/m2/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:./build/m2/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar
[echo] dep.org.apache.commons:commons-csv:jar = ./build/m2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar
BUILD SUCCESSFUL
Total time: 1 second
A second run finds everything in the local repository and prints only Resolving artifacts before the two echo lines.
Four things in this output are worth reading slowly:
-
The POMs come first. Maven Resolver reads
commons-csv-1.14.1.pomand its parents (commons-parent,apache) to learn the dependencies ofcommons-csvbefore it knows which JARs to download. TheResolving artifactsline marks the switch from graph building to downloading. -
Transitive dependencies are followed. The one declared dependency produced three JARs, because
commons-csvdepends oncommons-ioandcommons-codec. Withant -vthe task also prints the dependency graph, and the repository it uses:[resolver:resolve] Using local repository ./build/m2 (enhanced) Using remote repository central (https://repo.maven.apache.org/maven2/, default, releases) Collecting dependencies [resolver:resolve] Resolved Dependency Graph: [resolver:resolve] org.apache.commons:commons-csv:jar:1.14.1:compile [resolver:resolve] commons-io:commons-io:jar:2.20.0:compile [resolver:resolve] commons-codec:commons-codec:jar:1.19.0:compile [resolver:resolve] Resolving artifacts -
Three outputs were requested, and each is a different Ant object.
<path refid="compile.classpath">defines an Ant path, usable asclasspathrefinjavacandjava.<files refid="lib.files" dir="build/lib" layout="…">copies the files intobuild/libunder names built from the layout placeholders and also defines a resource collection with that id.<properties prefix="dep.">sets one property per artifact, named after the prefix and the artifact’sgroupId:artifactId:extension, whose value is the path of the file in the local repository (note the colons in the property names;${dep.org.apache.commons:commons-csv:jar}works in a build file, but the name cannot be built with a macro attribute without care). Thebuild/libdirectory ended with the three expected files,commons-codec-1.19.0.jar,commons-csv-1.14.1.jarandcommons-io-2.20.0.jar. -
The path points into the local repository, not into
build/lib. The classpath uses the cache files directly; the copies inbuild/libexist for tasks that want a directory (packaging a WAR, an application distribution).
Settings, repositories and mirrors
Where the settings come from
The tasks read the same settings.xml as Maven. By default the user file is ${user.home}/.m2/settings.xml; for the global
file the site lists ${ant.home}/etc/settings.xml and ${maven.home}/conf/settings.xml. The <settings> type changes
both locations. The global-file search order is taken from the site and was not run here. With no settings file and no
localrepo, the local repository was the Maven default, ~/.m2/repository: the first, throw-away experiment of this page
resolved commons-csv-1.14.1.jar to /Users/…/.m2/repository/org/apache/commons/commons-csv/1.14.1/…. That sharing is
usually what a developer wants, because Maven and Ant builds reuse each other’s downloads, and sometimes what a CI job does
not want, so a project-local <localrepo dir="build/m2"/> (as used in this page) is the way to isolate it.
A settings file can supply the local repository, mirrors, servers (credentials) and proxies. This settings.xml was used in
the next example; the credentials are placeholders that read environment variables, and the example does not contact any
server that needs them, so credential handling was not run here:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<localRepository>${user.dir}/build/m2-from-settings</localRepository>
<mirrors>
<mirror>
<id>tidepool-mirror</id>
<mirrorOf>central</mirrorOf>
<url>https://repo.maven.apache.org/maven2/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>internal</id>
<username>${env.REPO_USER}</username>
<password>${env.REPO_TOKEN}</password>
</server>
</servers>
</settings>
<?xml version="1.0" encoding="UTF-8"?>
<project name="settings-mirror" default="resolve" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:settings file="settings/settings.xml"/>
<target name="resolve">
<resolver:resolve>
<dependencies>
<dependency coords="info.picocli:picocli:4.7.7"/>
</dependencies>
<path refid="cp" classpath="compile"/>
</resolver:resolve>
<echo>${toString:cp}</echo>
</target>
</project>
Buildfile: ./settings-mirror.xml
resolve:
[resolver:resolve] Downloading from tidepool-mirror https://repo.maven.apache.org/maven2/info/picocli/picocli/4.7.7/picocli-4.7.7.pom
[resolver:resolve] Downloaded from tidepool-mirror https://repo.maven.apache.org/maven2/info/picocli/picocli/4.7.7/picocli-4.7.7.pom (2 KB at 8.8 KB/sec)
[resolver:resolve] Resolving artifacts
[resolver:resolve] Downloading from tidepool-mirror https://repo.maven.apache.org/maven2/info/picocli/picocli/4.7.7/picocli-4.7.7.jar
[resolver:resolve] Downloaded from tidepool-mirror https://repo.maven.apache.org/maven2/info/picocli/picocli/4.7.7/picocli-4.7.7.jar (408 KB at 3315.9 KB/sec)
[echo] ./build/m2-from-settings/info/picocli/picocli/4.7.7/picocli-4.7.7.jar
Three effects of the settings file are visible: the JAR landed in the localRepository named there (with ${user.dir}
expanded to Ant’s working directory), the repository that Maven’s central id stands for was replaced by the mirror
(Downloading from tidepool-mirror), and the build file itself did nothing about repositories. In real use the mirror’s URL
would be an internal repository manager and the central URL would never be contacted directly; here the mirror points back
to Central so that the example runs anywhere. Mirrors, servers, encrypted passwords and profiles are described for Maven on
settings.xml & Password
Encryption and
Repositories, Mirrors &
Repository Managers. The uber JAR bundles Maven’s settings decrypter and plexus-sec-dispatcher, so encrypted passwords in
settings.xml are expected to work as in Maven; that was not tested.
The repository and network types
These types can be declared at the project level (then they apply to every task) or nested in a task. Attribute names come from the site and from the classes in the JAR.
| Element | Attributes and notes |
|---|---|
|
|
|
|
|
A named group of |
|
|
|
|
|
|
|
|
The project-level declaration of two repositories and a group, taken from the consuming build that appears later in this page, shows the syntax:
<resolver:localrepo dir="build/m2-consumer"/>
<resolver:remoterepo id="tidepool.local" url="file://${basedir}/build/repo" snapshots="false" checksums="fail"/>
<resolver:remoterepos id="all">
<remoterepo refid="tidepool.local"/>
<remoterepo id="central" url="https://repo.maven.apache.org/maven2/" snapshots="false"/>
</resolver:remoterepos>
Which repositories does resolve use? The task uses the repositories nested in it or referenced with remoteReposRef; if it
has none, it uses the group with the id resolver.repositories, which by default contains only Central. Declaring your own
<remoterepos id="resolver.repositories"> replaces that default for every resolve in the build:
<resolver:remoterepos id="resolver.repositories">
<remoterepo id="tidepool.local" url="file://${basedir}/build/repo"/>
<remoterepo id="central" url="https://repo.maven.apache.org/maven2/"/>
</resolver:remoterepos>
Run with ant -v, the task then reported Using remote repository tidepool.local (file://./build/repo, default, releases)
followed by Using remote repository central, and resolved io.tidepool:tidepool-engine:1.0.0 from the first.
Explicit repositories do not stop Central from being used for transitive dependencies. In the checksum experiment
below, resolve was given only one repository, a file repository holding only the tidepool artifacts. The tidepool POM was read from it, but commons-csv
and its parents came from Central:
[resolver:resolve] Downloaded from bad file://./build/repo-bad/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[resolver:resolve] Downloaded from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom
…
Maven works the same way: the repositories declared inside a dependency’s POM, including the central repository of the
Super POM that every POM inherits, are added when that dependency’s own dependencies are collected. A build that must not
contact Central has to say so with a mirror (<mirrorOf>*</mirrorOf>) in the settings file, not only with its own
repository list. For a build that has no mirror a safe check is an offline run, below.
Offline mode
Setting the property resolver.offline to true suppresses all network traffic: only artifacts and metadata already in
the local repository are used. It can be set in the build file or, as here, on the command line. With an empty local
repository the build fails, with a message that names the cause; with a populated one it succeeds:
rm -rf build/m2
ant -Dresolver.offline=true -f resolve-basic.xml # cold
ant -f resolve-basic.xml # fill the cache
ant -Dresolver.offline=true -f resolve-basic.xml # warm
Buildfile: ./resolve-basic.xml
resolve:
[resolver:resolve] The POM for org.apache.commons:commons-csv:jar:1.14.1 is missing, no dependency information available
[resolver:resolve] Resolving artifacts
BUILD FAILED
./resolve-basic.xml:11: Could not resolve artifacts: The following artifacts could not be resolved: org.apache.commons:commons-csv:jar:1.14.1 (absent): Cannot access central (https://repo.maven.apache.org/maven2/) in offline mode and the artifact org.apache.commons:commons-csv:jar:1.14.1 has not been downloaded from it before.
Total time: 0 seconds
The warm run prints the same Resolving artifacts and echo lines as an online run, with no download lines. That makes
resolver.offline the cheap way to prove that a build can run from its cache, which is what a CI job with a restored cache
and no network access needs. Gradle’s --offline and Maven’s -o are the same idea in the other two tools; see
Repositories & Publishing Across
Tools.
Declaring dependencies
Dependencies are Ant types, so they can be declared once, given an id and reused by several resolve tasks. A
<dependency> has two spellings that mean the same:
<dependency coords="org.apache.commons:commons-csv:1.14.1"/>
<dependency groupId="org.apache.commons" artifactId="commons-csv" version="1.14.1"
classifier="" type="jar" scope="compile">
<exclusion groupId="commons-codec" artifactId="commons-codec"/>
</dependency>
The coords format is groupId:artifactId:version[[:type[:classifier]]:scope]: the type and classifier are optional, the
scope is last. The attributes are groupId, artifactId, version, classifier, type, scope and systemPath (for
system scope dependencies), and nested <exclusion> elements take groupId, artifactId, classifier, extension or
coords (g:a). A <dependencies> element groups dependencies, global <exclusion> elements and other <dependencies>
groups (refid), and can be filled from a POM or from a text file. The three sources of dependencies can be mixed.
From a POM
If the project already has a pom.xml, the pom task registers it and <dependencies pomRef="…"/> takes its dependencies,
after property interpolation. This is the cheapest bridge between a Maven project and an Ant script that must build with the
same dependencies, and the POM can be as small as the one used here:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-engine</artifactId>
<version>1.0.0</version>
<name>Tidepool engine</name>
<properties>
<csv.version>1.14.1</csv.version>
<release.target>17</release.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${csv.version}</version>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.7.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project name="pom-based" default="paths" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2"/>
<resolver:pom file="pom.xml" id="pom"/>
<target name="paths">
<echo>pom.groupId = ${pom.groupId}</echo>
<echo>pom.artifactId = ${pom.artifactId}</echo>
<echo>pom.version = ${pom.version}</echo>
<echo>pom.name = ${pom.name}</echo>
<echo>pom.properties.csv.version = ${pom.properties.csv.version}</echo>
<resolver:resolve>
<dependencies pomRef="pom"/>
<path refid="cp.compile" classpath="compile"/>
<path refid="cp.runtime" classpath="runtime"/>
<path refid="cp.test" classpath="test"/>
</resolver:resolve>
<pathconvert property="c.compile" refid="cp.compile" pathsep=" "><mapper type="flatten"/></pathconvert>
<pathconvert property="c.runtime" refid="cp.runtime" pathsep=" "><mapper type="flatten"/></pathconvert>
<pathconvert property="c.test" refid="cp.test" pathsep=" "><mapper type="flatten"/></pathconvert>
<echo>compile: ${c.compile}</echo>
<echo>runtime: ${c.runtime}</echo>
<echo>test : ${c.test}</echo>
</target>
</project>
Buildfile: ./pom-based.xml
paths:
[echo] pom.groupId = io.tidepool
[echo] pom.artifactId = tidepool-engine
[echo] pom.version = 1.0.0
[echo] pom.name = Tidepool engine
[echo] pom.properties.csv.version = 1.14.1
[resolver:resolve] Resolving artifacts
[echo] compile: commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar picocli-4.7.7.jar
[echo] runtime: commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar
[echo] test : commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar picocli-4.7.7.jar junit-jupiter-api-6.1.3.jar opentest4j-1.3.0.jar junit-platform-commons-6.1.3.jar apiguardian-api-1.1.2.jar jspecify-1.0.0.jar
BUILD SUCCESSFUL
(The first run of this build printed the usual download lines before Resolving artifacts; they are left out.)
This run answers most questions about the pom type and about scopes:
-
Properties. A POM registered from a
filemakes its effective model available as Ant properties, prefixed with theidof the<pom>element (pom.when it has none):${pom.groupId},${pom.artifactId},${pom.version},${pom.name}, and the POM’s own<properties>as${pom.properties.csv.version}. The build file never repeats the version, which is why a build that publishes (below) can name its JAR frompom.artifactIdandpom.version. A<pom>can also be declared without a file, withgroupId,artifactId,versionandpackaging, or withcoords="g:a:v"; then there is no POM file to install, andinstallanddeployrefuse to run (see the errors table). -
The default POM. A
<pom>built from a file becomes the default forinstall,deployandresolvewhen it has noid, and also when it has one but no default POM exists yet (Pom.execute()in 2.0.0); a later<pom>with aniddoes not replace an existing default, a later one without aniddoes. The log saysUsing default POM (…)when that happens. The default id isresolver.pom, a constant in the JAR. -
Scope filters. The
classpathshortcut on a<path>,<files>or<properties>target is shorthand for a list of scopes. The site definesclasspath="compile"asprovided,system,compile. In the run abovecompileincludes theprovidedlibrarypicocliand leaves outjunit-jupiter-api;runtimedropspicoclibecauseprovidedis not on the runtime classpath; andtesthas everything.providedfollows Maven: it is on the compile and test classpaths, and not on the runtime one. -
Transitive scope. The transitive dependencies of
junit-jupiter-api(opentest4j,junit-platform-commons,apiguardian-apiandjspecify) arrived with the test scope of the dependency that brought them.
From a plain text file, scope filters and exclusions
For an Ant-only project a POM is overhead. A dependency can come from a file with one dependency per line, in the coords
format; everything after a hash is a comment, and an empty line is skipped:
# Tidepool engine dependencies, one per line: groupId:artifactId:version[:type[:classifier]]:scope
org.apache.commons:commons-csv:1.14.1:compile
info.picocli:picocli:4.7.7:provided # supplied by the launcher
org.junit.jupiter:junit-jupiter-api:6.1.3:test
The scope filters can name scopes directly, with ! (or -) to exclude one, and exclusions can be attached to one
dependency or to the whole group. This build file shows both, and a macro that prints a path in a readable way:
<?xml version="1.0" encoding="UTF-8"?>
<project name="scopes-and-exclusions" default="all" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2"/>
<macrodef name="show">
<attribute name="label"/>
<attribute name="refid"/>
<sequential>
<pathconvert property="out.@{label}" refid="@{refid}" pathsep=" "><mapper type="flatten"/></pathconvert>
<echo>@{label}: ${out.@{label}}</echo>
</sequential>
</macrodef>
<target name="file">
<resolver:resolve>
<dependencies file="dependencies.txt"/>
<path refid="p.compile" classpath="compile"/>
<path refid="p.only.test" scopes="test"/>
<path refid="p.no.provided" scopes="compile,runtime,!provided"/>
<properties prefix="provided." scopes="provided"/>
</resolver:resolve>
<show label="compile" refid="p.compile"/>
<show label="test-only" refid="p.only.test"/>
<show label="no-provided" refid="p.no.provided"/>
<echo>provided.info.picocli:picocli:jar = ${provided.info.picocli:picocli:jar}</echo>
</target>
<target name="exclusions">
<resolver:resolve>
<dependencies>
<dependency coords="org.apache.commons:commons-csv:1.14.1">
<exclusion groupId="commons-codec" artifactId="commons-codec"/>
</dependency>
<exclusion coords="commons-io:commons-io"/>
</dependencies>
<path refid="p.excl" classpath="runtime"/>
</resolver:resolve>
<show label="after exclusions" refid="p.excl"/>
</target>
<target name="all" depends="file,exclusions"/>
</project>
Buildfile: ./scopes-and-exclusions.xml
file:
[resolver:resolve] Resolving artifacts
[echo] compile: commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar picocli-4.7.7.jar
[echo] test-only: junit-jupiter-api-6.1.3.jar opentest4j-1.3.0.jar junit-platform-commons-6.1.3.jar apiguardian-api-1.1.2.jar jspecify-1.0.0.jar
[echo] no-provided: commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar
[echo] provided.info.picocli:picocli:jar = ./build/m2/info/picocli/picocli/4.7.7/picocli-4.7.7.jar
exclusions:
[resolver:resolve] Resolving artifacts
[echo] after exclusions: commons-csv-1.14.1.jar
all:
BUILD SUCCESSFUL
scopes="test" selects only the test-scoped artifacts (not the compile ones, which classpath="test" would include), and
scopes="compile,runtime,!provided" states the set directly. Both the per-dependency exclusion (commons-codec) and the
global one (commons-io) removed their artifact from the graph, leaving only commons-csv. The <properties scopes="…">
form is the cheapest way to get the path of one specific file, such as a provided launcher JAR for a java task.
Versions without a version: BOMs with dependencyManagement
Since 1.6.0 the dependencyManagement type brings Maven’s dependency management to an Ant build: a BOM imported with
type="pom" scope="import" supplies the versions that dependency elements leave out. It is combined with the
dependencies by a registered POM, which createPom produces (next section). The 2.0.0 run shows both sides: the dependency
junit-jupiter-api has no version attribute, and the JUnit BOM 6.1.3 provided it.
The management only takes effect when the task reads it from a registered POM. An unversioned dependency in a plain
<dependencies> block, with no management behind it, fails before anything is downloaded:
### ant noversion
BUILD FAILED
./errors.xml:14: You must specify the 'version' for a dependency
The same failure appeared in the createPom build below when its last resolve was pointed at the dependency group
(dependenciesRef) instead of at the registered POM; with the POM, the unversioned dependency resolved. For the Maven-side mechanics see
Dependency Management & BOMs.
The resolve task in detail
<resolve> has one required input, <dependencies> (nested, or dependenciesRef, or the default POM) and any number of
outputs.
| Element or attribute | Meaning |
|---|---|
|
The dependencies to resolve. At most one per task. Without one, the default POM is used; with neither, the build fails with |
|
Repositories to use for this task instead of |
|
Fail when a requested |
|
Defines an Ant |
|
Defines a resource collection with |
|
Sets one property per artifact, prefix first, then |
Each of the three targets can appear more than once in the same task, which is how the examples above produced the compile, runtime and test paths from one dependency graph and one set of downloads.
Attachments: sources and Javadoc
attachments="sources" or "javadoc" asks for the -sources or -javadoc classifier of every resolved artifact. That is
how an Ant build feeds an IDE or a documentation task. Many artifacts do not publish both, so the behaviour for the missing
ones is configurable. ant-launcher 1.10.15 has sources but no Javadoc on Central:
<?xml version="1.0" encoding="UTF-8"?>
<project name="attachments" default="sources" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2"/>
<target name="sources">
<resolver:resolve failOnMissingAttachments="${strict}">
<dependencies>
<dependency coords="org.apache.ant:ant-launcher:1.10.15"/>
</dependencies>
<files dir="build/attachments" attachments="sources" layout="{artifactId}-{version}-{classifier}.{extension}"/>
<files dir="build/attachments" attachments="javadoc" layout="{artifactId}-{version}-{classifier}.{extension}"/>
</resolver:resolve>
</target>
</project>
$ ant -q -Dstrict=false -f attachments.xml
BUILD SUCCESSFUL
Total time: 0 seconds
$ ls build/attachments
ant-launcher-1.10.15-sources.jar
$ ant -q -Dstrict=true -f attachments.xml
BUILD FAILED
./attachments.xml:10: Could not resolve artifacts: The following artifacts could not be resolved: org.apache.ant:ant-launcher:jar:javadoc:1.10.15 (absent): Could not find artifact org.apache.ant:ant-launcher:jar:javadoc:1.10.15 in central (https://repo.maven.apache.org/maven2/)
Without the attribute the behaviour was the same as false: the build succeeded and only the sources JAR was copied.
Checksum and update policies
A repository’s checksums policy decides what happens when the checksum next to a downloaded file does not match: fail,
warn or ignore. To see all three, a copy of the repository that the publishing build below creates was damaged on
purpose (a wrong .sha1 for the JAR and no .md5), and the same resolve was run against it:
<?xml version="1.0" encoding="UTF-8"?>
<project name="chk" default="go" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2-chk"/>
<target name="go">
<resolver:resolve>
<dependencies><dependency coords="io.tidepool:tidepool-engine:1.0.0:runtime"/></dependencies>
<remoterepo id="bad" url="file://${basedir}/build/repo-bad" checksums="${policy}"/>
<path refid="p"/>
</resolver:resolve>
<echo>${toString:p}</echo>
</target>
</project>
$ ant -q -Dpolicy=fail -f checksums.xml
[resolver:resolve] Checksum validation failed, expected 'deadbeef' (REMOTE_EXTERNAL) but is actually '244aba0fdd648a120aa1101817b095247e3251d7' for file://./build/repo-bad/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
BUILD FAILED
./checksums.xml:9: Could not resolve artifacts: The following artifacts could not be resolved: io.tidepool:tidepool-engine:jar:1.0.0 (absent): Could not transfer artifact io.tidepool:tidepool-engine:jar:1.0.0 from/to bad (file://./build/repo-bad): Checksum validation failed, expected 'deadbeef' (REMOTE_EXTERNAL) but is actually '244aba0fdd648a120aa1101817b095247e3251d7'
$ ant -q -Dpolicy=warn -f checksums.xml
[resolver:resolve] Checksum validation failed, expected 'deadbeef' (REMOTE_EXTERNAL) but is actually '244aba0fdd648a120aa1101817b095247e3251d7' for file://./build/repo-bad/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[resolver:resolve] Checksum validation failed, expected 'deadbeef' (REMOTE_EXTERNAL) but is actually '244aba0fdd648a120aa1101817b095247e3251d7' for file://./build/repo-bad/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[echo] ./build/m2-chk/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar:…
$ ant -q -Dpolicy=ignore -f checksums.xml
[echo] ./build/m2-chk/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar:…
With warn the build continues and the warning is printed twice (as observed); with ignore nothing is printed. Use fail
for anything that is not a throw-away experiment. Notice that these checksums protect against corruption in transit, not
against a malicious repository, which can serve a matching pair. The updates attribute (always, daily, never, or
an interval in minutes) controls how often Resolver re-checks remote metadata, mainly for SNAPSHOT versions; it was not
exercised here.
Publishing: install and deploy
Where resolve reads, install and deploy write. Both need to know what is being published (the artifacts) and as
what (the POM that gives group, artifact and version). The artifacts are <artifact file="…" type="…" classifier="…"/>
elements, nested or grouped under an <artifacts id="…"> and referenced with artifactsRef. The POM is a <pom file="…"/>
registered earlier, nested in the task, or referenced with pomRef. A <pom> that has a file is required: the site states
"you need to set a POM that references a file", because the POM file itself is uploaded next to the artifacts.
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool" default="install" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<property name="build.dir" location="build"/>
<resolver:pom file="pom.xml" id="pom"/>
<target name="package">
<mkdir dir="${build.dir}/classes"/>
<javac srcdir="src/main/java" destdir="${build.dir}/classes" release="17" includeantruntime="false"/>
<jar destfile="${build.dir}/${pom.artifactId}-${pom.version}.jar" basedir="${build.dir}/classes"/>
<jar destfile="${build.dir}/${pom.artifactId}-${pom.version}-sources.jar" basedir="src/main/java"/>
</target>
<target name="install" depends="package">
<resolver:localrepo dir="${build.dir}/m2-install"/>
<resolver:install>
<artifact file="${build.dir}/${pom.artifactId}-${pom.version}.jar"/>
<artifact file="${build.dir}/${pom.artifactId}-${pom.version}-sources.jar" classifier="sources"/>
</resolver:install>
</target>
<target name="deploy" depends="package">
<resolver:localrepo dir="${build.dir}/m2-install"/>
<resolver:remoterepo id="local.release" url="file://${build.dir}/repo"/>
<resolver:deploy>
<remoterepo refid="local.release"/>
<artifact file="${build.dir}/${pom.artifactId}-${pom.version}.jar"/>
<artifact file="${build.dir}/${pom.artifactId}-${pom.version}-sources.jar" classifier="sources"/>
</resolver:deploy>
</target>
</project>
The POM is the one shown earlier, and the Java source is a single record io.tidepool.Station. The pom.artifactId and
pom.version properties come from the registered POM.
Source: Maven Resolver Ant Tasks: Install; Maven Resolver Ant Tasks: Deploy; Maven Resolver Ant Tasks: Output Artifacts
install
install copies the artifacts into the local repository in the Maven layout, which is the repository that Maven and Gradle’s
mavenLocal() read when they use the same directory (the ~/.m2/repository default); here it is build/m2-install.
ant -f publish.xml install
Buildfile: ./publish.xml
package:
[mkdir] Created dir: ./build/classes
[javac] Compiling 1 source file to ./build/classes
[jar] Building jar: ./build/tidepool-engine-1.0.0.jar
[jar] Building jar: ./build/tidepool-engine-1.0.0-sources.jar
install:
[resolver:install] Using default POM (io.tidepool:tidepool-engine:1.0.0)
[resolver:install] Installing ./pom.xml to ./build/m2-install/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[resolver:install] Installing ./build/tidepool-engine-1.0.0.jar to ./build/m2-install/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[resolver:install] Installing ./build/tidepool-engine-1.0.0-sources.jar to ./build/m2-install/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar
[resolver:install] Installing io.tidepool:tidepool-engine/maven-metadata.xml to ./build/m2-install/io/tidepool/tidepool-engine/maven-metadata-local.xml
BUILD SUCCESSFUL
Total time: 0 seconds
Two points deserve notice. The file name of the installed POM is derived from the coordinates (tidepool-engine-1.0.0.pom)
and its content is the source pom.xml copied as it is, including ${csv.version}; consumers that read it let Maven’s
model builder interpolate it. (The next section shows a generated POM that is complete without interpolation.) And the
installed directory holds more than the artifacts: _remote.repositories, which records where each file came from,
maven-metadata-local.xml, and under the repository root .meta/resolver-status.properties and a
resolver-status.properties, which Resolver uses to remember failed lookups.
deploy
deploy uploads to the remote repository nested in it, and (for a SNAPSHOT version) to a separate snapshotrepo. The
checksum files and the repository metadata are generated by the task. For this page the only target is a file:// URL
under build; publishing to a real server or to Maven Central was not run here, and credentials for one would go in an
<authentication> element with placeholders such as ${env.REPO_TOKEN}, never in the file.
ant -f publish.xml deploy
find build/repo -type f | sort
Buildfile: ./publish.xml
package:
deploy:
[resolver:deploy] Using default POM (io.tidepool:tidepool-engine:1.0.0)
[resolver:deploy] Uploading to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[resolver:deploy] Uploaded to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom (936 B at 304.7 KB/sec)
[resolver:deploy] Uploading to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[resolver:deploy] Uploading to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar
[resolver:deploy] Uploaded to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar (725 B at 708.0 KB/sec)
[resolver:deploy] Uploaded to local.release file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar (2 KB at 1249.0 KB/sec)
[resolver:deploy] Downloading from local.release file://./build/repo/io/tidepool/tidepool-engine/maven-metadata.xml
[resolver:deploy] Uploading to local.release file://./build/repo/io/tidepool/tidepool-engine/maven-metadata.xml
[resolver:deploy] Uploaded to local.release file://./build/repo/io/tidepool/tidepool-engine/maven-metadata.xml (306 B)
BUILD SUCCESSFUL
Total time: 0 seconds
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar.md5
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar.sha1
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar.md5
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar.sha1
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom.md5
build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom.sha1
build/repo/io/tidepool/tidepool-engine/maven-metadata.xml
build/repo/io/tidepool/tidepool-engine/maven-metadata.xml.md5
build/repo/io/tidepool/tidepool-engine/maven-metadata.xml.sha1
The layout is the standard Maven repository layout, with .md5 and .sha1 checksum files next to every file and the
artifact-level maven-metadata.xml (in the tidepool-engine directory, not the group’s) that lists the released versions. What is not produced is PGP signatures (.asc) or the
other checks that Maven Central demands, so this task alone cannot publish to Central; the Maven side of that is on
Publishing to Maven Central, and the
layout is drawn on
Repositories & Publishing Across
Tools. Only file:, http: and https: repositories are supported; a ftp:// URL failed, as shown in the errors table.
Source: Maven Resolver Ant Tasks: Deploy
Releases and SNAPSHOTs
deploy sends a SNAPSHOT version to the <snapshotrepo> and a release version to the <remoterepo>, as Maven’s
distributionManagement does. A SNAPSHOT upload is timestamped, and a second upload does not replace the first. The deploy
below uses the POM snapshot-pom.xml, which has the version 1.1.0-SNAPSHOT:
<?xml version="1.0" encoding="UTF-8"?>
<project name="snapshots" default="deploy" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<property name="build.dir" location="build"/>
<resolver:localrepo dir="build/m2-snap"/>
<resolver:pom file="snapshot-pom.xml"/>
<target name="deploy">
<mkdir dir="${build.dir}/classes"/>
<jar destfile="${build.dir}/tidepool-model-1.1.0-SNAPSHOT.jar" basedir="${build.dir}/classes" whenempty="create"/>
<resolver:remoterepo id="releases" url="file://${build.dir}/repo-snapshots/releases"/>
<resolver:remoterepo id="snapshots" url="file://${build.dir}/repo-snapshots/snapshots"/>
<resolver:deploy>
<remoterepo refid="releases"/>
<snapshotrepo refid="snapshots"/>
<artifact file="${build.dir}/tidepool-model-1.1.0-SNAPSHOT.jar"/>
</resolver:deploy>
</target>
</project>
Buildfile: ./snapshots.xml
deploy:
[mkdir] Created dir: ./build/classes
[jar] JARs are never empty, they contain at least a manifest file
[jar] Building MANIFEST-only jar: ./build/tidepool-model-1.1.0-SNAPSHOT.jar
[resolver:deploy] Using default POM (io.tidepool:tidepool-model:1.1.0-SNAPSHOT)
[resolver:deploy] Downloading from snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/maven-metadata.xml
[resolver:deploy] Uploading to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/tidepool-model-1.1.0-20261017.100531-1.pom
[resolver:deploy] Uploaded to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/tidepool-model-1.1.0-20261017.100531-1.pom (250 B at 122.1 KB/sec)
[resolver:deploy] Uploading to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/tidepool-model-1.1.0-20261017.100531-1.jar
[resolver:deploy] Uploaded to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/tidepool-model-1.1.0-20261017.100531-1.jar (332 B at 324.2 KB/sec)
[resolver:deploy] Downloading from snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/maven-metadata.xml
[resolver:deploy] Uploading to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/maven-metadata.xml
[resolver:deploy] Uploaded to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/1.1.0-SNAPSHOT/maven-metadata.xml (775 B at 756.8 KB/sec)
[resolver:deploy] Uploading to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/maven-metadata.xml
[resolver:deploy] Uploaded to snapshots file://./build/repo-snapshots/snapshots/io/tidepool/tidepool-model/maven-metadata.xml (285 B)
BUILD SUCCESSFUL
Total time: 0 seconds
A second run, in the same second, produced tidepool-model-1.1.0-20261017.100531-2.jar (and its .pom) next to the -1 files. The release repository
(repo-snapshots/releases) was not touched. (The run above printed Downloading from snapshots …/maven-metadata.xml for
the not yet existing metadata, which is how Resolver reads the previous build number.)
Consuming what was published
The repository that deploy wrote can be used straight away as a remote repository by another build, even a Maven or Gradle
one. This consumer lists it before Central, uses a different local repository (so nothing is taken from the earlier
install), and asks for the sources of everything:
<?xml version="1.0" encoding="UTF-8"?>
<project name="consumer" default="consume" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<resolver:localrepo dir="build/m2-consumer"/>
<resolver:remoterepo id="tidepool.local" url="file://${basedir}/build/repo" snapshots="false" checksums="fail"/>
<resolver:remoterepos id="all">
<remoterepo refid="tidepool.local"/>
<remoterepo id="central" url="https://repo.maven.apache.org/maven2/" snapshots="false"/>
</resolver:remoterepos>
<target name="consume">
<resolver:resolve>
<dependencies>
<dependency coords="io.tidepool:tidepool-engine:1.0.0"/>
</dependencies>
<remoterepos refid="all"/>
<path refid="cp" classpath="runtime"/>
<files dir="build/consumed-sources" attachments="sources" layout="{artifactId}-{classifier}.{extension}"/>
</resolver:resolve>
<pathconvert property="cp.flat" refid="cp" pathsep=" "><mapper type="flatten"/></pathconvert>
<echo>runtime classpath: ${cp.flat}</echo>
</target>
</project>
Buildfile: ./consume.xml
consume:
[resolver:resolve] Downloading from tidepool.local file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[resolver:resolve] Downloaded from tidepool.local file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom (936 B at 457.0 KB/sec)
[resolver:resolve] Downloading from tidepool.local file://./build/repo/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom
[resolver:resolve] Downloading from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom
[resolver:resolve] Downloaded from central https://repo.maven.apache.org/maven2/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.pom (22 KB at 578.3 KB/sec)
…
[resolver:resolve] Resolving artifacts
[resolver:resolve] Downloading from tidepool.local file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[resolver:resolve] Downloaded from tidepool.local file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar (2 KB at 1249.0 KB/sec)
…
[resolver:resolve] Downloaded from tidepool.local file://./build/repo/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0-sources.jar (725 B at 708.0 KB/sec)
…
[echo] runtime classpath: tidepool-engine-1.0.0.jar commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar
BUILD SUCCESSFUL
Total time: 1 second
The tidepool artifact came from the file repository, and commons-csv and the other third-party libraries from Central
(each lookup tries the repositories in order and the first that has the file wins, so the log shows a Downloading from
tidepool.local attempt first for every file). The directory build/consumed-sources held tidepool-engine-sources.jar
and the three sources JARs of the libraries. The same repository can be read from Maven with a <repository> element and
from Gradle with maven { url = uri("…/build/repo") }; this is the cross-tool round trip described on
Repositories & Publishing Across
Tools.
Generating a POM with createPom
A published artifact needs a POM so that consumers can resolve its dependencies, and an Ant-only project does not have one.
The createPom task writes one from the Ant-side description: group, artifact and version, name and description, the
dependencies and dependencyManagement types by reference, and nested licenses, developers, repositories and scm
elements. It also registers the POM it wrote as the default one, unless skipPomRegistration is true. The task is not
described on the site’s main page; its attributes (pomTarget, groupId, artifactId, version, name, description,
dependenciesRef, dependencyManagementRef, skipPomRegistration) come from the usage guide and the API.
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool" default="publish" basedir="."
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
<taskdef uri="antlib:org.apache.maven.resolver.ant"
resource="org/apache/maven/resolver/ant/antlib.xml"
classpath="lib/maven-resolver-ant-tasks-2.0.0-uber.jar"/>
<property name="groupId" value="io.tidepool"/>
<property name="artifactId" value="tidepool-engine"/>
<property name="version" value="1.0.0"/>
<property name="build.dir" location="build"/>
<resolver:localrepo dir="build/m2-pom"/>
<resolver:dependencyManagement id="dm">
<dependencies>
<dependency groupId="org.junit" artifactId="junit-bom" version="6.1.3" type="pom" scope="import"/>
</dependencies>
</resolver:dependencyManagement>
<resolver:dependencies id="deps">
<dependency coords="org.apache.commons:commons-csv:1.14.1"/>
<dependency groupId="org.junit.jupiter" artifactId="junit-jupiter-api" scope="test"/>
</resolver:dependencies>
<target name="pom">
<mkdir dir="${build.dir}"/>
<resolver:createPom pomTarget="${build.dir}/${artifactId}-${version}.pom"
groupId="${groupId}" artifactId="${artifactId}" version="${version}"
name="Tidepool engine" description="Tide prediction engine"
dependenciesRef="deps" dependencyManagementRef="dm">
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</resolver:createPom>
<echo message="registered POM: ${pom.groupId}:${pom.artifactId}:${pom.version}"/>
</target>
<target name="publish" depends="pom">
<mkdir dir="${build.dir}/classes"/>
<javac srcdir="src/main/java" destdir="${build.dir}/classes" release="17" includeantruntime="false"/>
<jar destfile="${build.dir}/${artifactId}-${version}.jar" basedir="${build.dir}/classes"/>
<resolver:install>
<artifact file="${build.dir}/${artifactId}-${version}.jar"/>
</resolver:install>
<resolver:resolve>
<path refid="cp.test" classpath="test"/>
</resolver:resolve>
<pathconvert property="cp.flat" refid="cp.test" pathsep=" "><mapper type="flatten"/></pathconvert>
<echo>test classpath: ${cp.flat}</echo>
</target>
</project>
The generated POM, which is what install then copied:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-engine</artifactId>
<version>1.0.0</version>
<name>Tidepool engine</name>
<description>Tide prediction engine</description>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.14.1</version>
<classifier></classifier>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
The empty <classifier></classifier> on the coords-declared dependency is how 2.0.0 wrote it (the dependency that was
declared with attributes did not get one); Maven treats an empty classifier as none, and the POM was usable, but it is a
blemish worth knowing if the file is checked in or diffed.
Buildfile: ./create-pom.xml
pom:
[mkdir] Created dir: ./build
[pom] Downloading from central https://repo.maven.apache.org/maven2/org/junit/junit-bom/6.1.3/junit-bom-6.1.3.pom
[pom] Downloaded from central https://repo.maven.apache.org/maven2/org/junit/junit-bom/6.1.3/junit-bom-6.1.3.pom (6 KB at 160.3 KB/sec)
[echo] registered POM: io.tidepool:tidepool-engine:1.0.0
publish:
[mkdir] Created dir: ./build/classes
[javac] Compiling 1 source file to ./build/classes
[jar] Building jar: ./build/tidepool-engine-1.0.0.jar
[resolver:install] Using default POM (io.tidepool:tidepool-engine:1.0.0)
[resolver:install] Installing ./build/tidepool-engine-1.0.0.pom to ./build/m2-pom/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[resolver:install] Installing ./build/tidepool-engine-1.0.0.jar to ./build/m2-pom/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[resolver:install] Installing io.tidepool:tidepool-engine/maven-metadata.xml to ./build/m2-pom/io/tidepool/tidepool-engine/maven-metadata-local.xml
[resolver:resolve] Using default pom for dependency resolution (io.tidepool:tidepool-engine:1.0.0 (org.apache.maven.resolver.internal.ant.types.Pom@12aa4996))
…
[resolver:resolve] Resolving artifacts
…
[echo] test classpath: commons-csv-1.14.1.jar commons-io-2.20.0.jar commons-codec-1.19.0.jar junit-jupiter-api-6.1.3.jar opentest4j-1.3.0.jar junit-platform-commons-6.1.3.jar apiguardian-api-1.1.2.jar jspecify-1.0.0.jar
BUILD SUCCESSFUL
Total time: 1 second
The last resolve has no <dependencies> at all: it used "the default pom for dependency resolution", the one createPom
had registered, with its imported BOM supplying the version of junit-jupiter-api. This is the whole round trip an Ant
project needs to publish a library with correct metadata: dependencies declared once, a POM generated from them, the
dependencies of the build resolved from the same declaration. A resolve that was given the unversioned dependency through
dependenciesRef instead of the registered POM stopped with You must specify the 'version' for a dependency.
Errors and how to read them
Most failures are one of a few messages. Each of these was produced on purpose with a small build file (errors.xml):
| Message | Cause | Fix |
|---|---|---|
|
The version does not exist in any repository that was searched. The wording is Maven Resolver’s, so a search for it finds Maven answers as well. |
Fix the coordinate, or add the repository that holds the artifact. |
|
|
Write |
|
A |
Add the version, or register a POM or |
|
A misspelled scope. This is only a warning: the build still succeeded. |
Use |
|
|
Register |
|
|
Add |
|
A repository URL with a protocol other than |
Use a supported protocol or put a repository manager in front. |
|
|
Run online once, or populate the local repository. |
|
A corrupted or tampered remote file with |
Fix the repository; do not weaken the policy. |
Two empty cases are silent. A resolve with dependencies but without <path>, <files> or <properties> resolves and
discards (it ran successfully and produced nothing, which is how a "pre-warm the cache" target can be written), and an
install with a registered POM but no artifacts also ended with BUILD SUCCESSFUL.
Ivy or the Maven Resolver Ant Tasks?
Both tools solve "an Ant build needs libraries from Maven repositories". The difference is the model and, with it, the amount of build-file machinery.
| Question | Maven Resolver Ant Tasks | Ivy 2.6.0 |
|---|---|---|
Dependency model |
Maven’s: coordinates, scopes, nearest-wins mediation, exclusions, BOMs |
Ivy’s: modules, arbitrary configurations and mappings between them, pluggable conflict managers and version matchers |
Where dependencies are declared |
In the build file, a text file or a |
In an |
Repositories and credentials |
The Maven |
|
Reports and caching |
The Maven local repository (shared with Maven); no HTML report, but |
Its own cache and an HTML/XML resolution report; very flexible retrieve patterns |
Publishing |
|
|
Setup |
One JAR, a namespace and about three lines |
One JAR, a namespace and usually an |
Best when |
The project is, or must interoperate with, a Maven project, or you want the least machinery |
The build needs Ivy’s configurations, its dependency reports or non-Maven repositories |
For a new Ant build that only consumes Maven Central, the Resolver Ant Tasks are the lower-ceremony choice, and the move from Ivy to them is walked through, with both versions run, in Migrating Between Tools. Ivy’s side is on Ivy: Settings & Resolvers, Ivy Ant Tasks and Ivy: Publishing & Multi-Project Builds.
The same thing in Maven and in Gradle
The tasks are Maven Resolver wrapped for Ant, so each has a direct Maven counterpart, and the Gradle equivalents are native features of that tool.
| Resolver Ant Tasks | Maven | Gradle |
|---|---|---|
|
|
A resolvable configuration such as |
|
|
A |
|
|
|
|
|
|
|
|
|
|
|
|
|
The |
The |
The engine |
Maven Resolver, described on Maven Resolver |
Gradle’s own resolution engine |
Where the books are outdated
Older Ant books and tutorials predate these tasks. They teach a lib/ directory with the JARs checked in, then the
older Maven Ant Tasks (maven-ant-tasks, last release 2.1.3, project site last published in 2014) or Ivy. The Resolver
Ant Tasks take over from the Maven Ant Tasks, and they are the route into Ant that the Maven project maintains today. Anything in
an older book that says "Ant has no dependency management of its own, so check the JARs in" is still true of Ant’s core, but
there are three maintained answers now: Ivy, the Resolver Ant Tasks, or a different tool (see
Deprecations & Migration).
References
-
Maven Resolver Ant Tasks: project site (version 2.0.0)
-
Maven Resolver Ant Tasks: usage.md
-
Maven Resolver Ant Tasks: examples
-
Maven Resolver Ant Tasks: Javadoc
-
Maven Resolver Ant Tasks: releases
-
Maven Central: maven-resolver-ant-tasks 2.0.0
-
Apache Maven: Settings reference
-
Apache Maven: Dependency Plugin: dependency:get
-
Apache Maven: Maven Ant Tasks (older project)
-
Apache Ant manual: Using Apache Ant
-
Apache Ant manual: taskdef