Side Quest: How Gradle Is Removing Ant
Table of Contents
Introduction
In October, the Gradle team spent a week together in Istanbul, and one of those days was a hackathon. One group picked a side quest with a deceptively short description: remove Ant from the Gradle distribution. Then we looked at gradle/gradle and we counted. Ant turned out to back 58 imports across 12 different Gradle features. So “remove Ant” became a deprecation in Gradle 9.9.0, a removal planned for Gradle 10, and a long new section in our upgrade guide.
TL;DR:
- Gradle 9.9.0 deprecates the built-in Ant integration, and we plan to remove it in Gradle 10.
- Gradle 10 also stops putting Ant classes on your build script classpath.
- Why: a modern Gradle build shouldn’t need Ant, and carrying it holds Gradle back.
- If your build calls
ant, the migration section covers how to move off it. - Gradle’s own internal Ant usage, like the PMD, CodeNarc, and Checkstyle plugins, goes away separately and isn’t tied to Gradle 10.
Why is Gradle removing Ant? #
Gradle’s Ant integration was largely about ease of migration. If your build was in Ant, you could bring it into Gradle and move it over piece by piece. That isn’t really a concern anymore.
We’ve asked whether Gradle should keep its Ant support for years, and this deprecation is our answer. A proper Gradle build shouldn’t need Ant for anything, and if we spot Ant in a build today, we recommend moving off it.
Where does Ant fit in “fast, flexible, scalable, comprehensible”? #
It doesn’t. We judge features against what Gradle is supposed to be, and native Ant integration isn’t on that list. Our job is to give you a platform where you can integrate Ant if you need it. The migration paths below do exactly that.
What does keeping Ant cost? #
Ant ships in every Gradle distribution, whether your build uses it or not.
It sits in the Gradle API JAR and on your build script classpath.
When you run gradle --version, Gradle prints the Ant version next to its own:
------------------------------------------------------------
Gradle 9.8.2
------------------------------------------------------------
Build time: 2026-10-19 16:29:26 UTC
Revision: 8de7bcf76e785507ad92f0741ab9235a430c95e6
Kotlin: 2.4.10
Groovy: 4.0.33
Ant: Apache Ant(TM) version 1.10.17 compiled on April 6 2026
Launcher JVM: 25 (Oracle Corporation 25+36-3489)
Daemon JVM: Compatible with Java 25, any vendor, nativeImageCapable=false (from gradle/gradle-daemon-jvm.properties)
OS: Mac OS X 15.8.1 aarch64
Carrying Ant around isn’t free:
- The Ant integration isn’t fully compatible with the Configuration Cache, and importing an Ant build turns the Configuration Cache off entirely.
- It ties our public API to Groovy, because most of what
AntBuilderdoes comes from Groovy’s ownAntBuilder. - Gradle bridges Ant’s logging into its own and runs some Ant tasks in isolated classloaders, just to keep Ant from interfering with everything else.
- Nobody picks Gradle for its Ant support, yet every hour we spend keeping Ant working is an hour we don’t spend on the parts of Gradle people do pick it for.
Where does Ant live in Gradle today? #
Here’s what the hackathon group found:
| # | Feature | Imports | What it backs |
|---|---|---|---|
| 1 | AntBuilder DSL |
12 | The ant { } DSL that lets build scripts call Ant tasks |
| 2 | Ant build logging adapter | 7 | Bridging Ant build events into Gradle logging |
| 3 | Ant types bridging | 7 | Converting Gradle file patterns (PatternFilterable) to Ant FileSet and resources |
| 4 | AntTarget task |
4 | Importing Ant targets as Gradle tasks |
| 5 | Default excludes (DirectoryScanner) |
7 | PatternSet default exclude patterns |
| 6 | Copy content filtering | 7 | filter(...) in Copy and CopySpec |
| 7 | Tar archive handling | 4 | Packing and extracting the wrapper distribution |
| 8 | TeeOutputStream |
3 | Tooling API test fixtures only |
| 9 | Bzip2 compression | 2 | bzip2 archive support |
| 10 | JAR manifest handling | 2 | DefaultManifest |
| 11 | Version reporting | 2 | The Ant line in gradle --version |
| 12 | Classloader handle | 1 | One worker test |
A few rows are small and easy to handle:
- Bzip2 moves to Commons Compress, and that PR already landed for Gradle 9.9.0.
- The version line just stops printing.
Rows 1 through 4 are the public Ant integration, and you might be using them, so they need a proper deprecation.
What’s deprecated? #
Gradle 9.9.0 ships the deprecation. These types and methods go away in Gradle 10:
AntBuilderAntTargetAntBuilderAwareProject.getAnt()Project.ant(Closure)Project.ant(Action)Project.createAntBuilder()Task.getAnt()FileCollection.addToAntBuilder(Object, String)FileCollection.addToAntBuilder(Object, String, AntType)FileCollection.AntTypePatternSet.addToAntBuilder(Object, String)
Gradle 10 also stops making Ant classes implicitly available to build scripts.
If you import org.apache.tools.ant... anywhere, that import will break unless you add Ant yourself.
What do I need to do? #
If your build scripts never call ant, never use AntTarget, and never import org.apache.tools.ant, you’re done.
Go get a coffee.
Everyone else needs to migrate each use of Ant. How to migrate depends on the usage, as the upgrade guide describes:
Switch to a Gradle equivalent #
Plenty of Ant usage already has a Gradle equivalent:
| Ant task | Gradle equivalent |
|---|---|
copy |
The Copy task, or FileSystemOperations.copy() inside a task action |
delete |
The Delete task, or FileSystemOperations.delete() |
zip, jar, tar |
The Zip, Jar, and Tar tasks |
exec |
The Exec task, or ExecOperations.exec() |
java |
The JavaExec task, or ExecOperations.javaexec() |
Watch for a few differences when you switch:
- Copying: by default, Ant’s
copyskips a file when the destination copy is newer. Gradle’sCopytask copies everything and relies on up-to-date checks instead. If the destination should mirror the source, including deleting stale files, useSync. - Exit codes: Ant’s
execignores a non-zero exit code unless you setfailonerror. Gradle’sExectask fails the build by default. SetisIgnoreExitValue = trueif you really want the old behavior. - Inputs and outputs: Gradle couldn’t see what an
ant.copycall insidedoLastread or wrote. With Gradle tasks, declare inputs and outputs so up-to-date checks and the build cache work.
Call the tool directly #
If you use Ant only to launch another tool, run that tool directly.
A JavaExec task can run its main class, or the Worker API can call its Java API with the tool on the classpath of the worker.
You can also put the tool on the classpath of your build logic and call its Java API from a task action.
Replace Ant filters with filter(Transformer) #
The most common way builds lean on Ant classes is filter(Map, Class) with an Ant FilterReader such as ReplaceTokens.
You can replace it with a plain filter { } that swaps every token in a single pass:
tasks.register<Copy>("processTemplates") {
from("src/templates")
into(layout.buildDirectory.dir("templates"))
val tokens = mapOf("version" to "1.0")
inputs.properties(tokens)
val pattern = Regex(tokens.keys.joinToString("|") { Regex.escape("@$it@") })
filter { line: String -> pattern.replace(line) { tokens.getValue(it.value.removeSurrounding("@")) } }
}
This replaces tokens the same way ReplaceTokens does, as long as no token key contains a line break.
If one does, write your own java.io.FilterReader and pass it to filter(Map, Class), or add Ant yourself.
Also, filter { } works one line at a time, so it’s a poor substitute for filters like HeadFilter and TailFilter.
Keep Ant’s filters by adding Ant yourself #
If you’d rather keep ReplaceTokens, put Ant on the classpath of your build logic.
In a build script, that’s the buildscript block:
import org.apache.tools.ant.filters.ReplaceTokens
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.apache.ant:ant:1.10.17")
}
}
tasks.register<Copy>("processTemplates") {
from("src/templates")
into(layout.buildDirectory.dir("templates"))
filter<ReplaceTokens>("tokens" to mapOf("version" to "1.0"))
}
If you use convention plugins, add implementation("org.apache.ant:ant:1.10.17") to the build that contains them instead.
Only do this starting with Gradle 10. Before that, it puts a second copy of Ant on the build script classpath, and Gradle’s own copy wins.
Run Ant in a separate process #
Some tools only ship an Ant task. For those, put Ant and the tool on their own classpath and run Ant in a separate Java process from a task:
val antTool = configurations.dependencyScope("antTool")
val antClasspath = configurations.resolvable("antClasspath") {
extendsFrom(antTool)
}
repositories {
mavenCentral()
}
dependencies {
antTool("org.apache.ant:ant:1.10.17")
antTool("org.glassfish.jaxb:jaxb-xjc:4.0.9")
}
tasks.register<GenerateJaxbClasses>("generateJaxbClasses") {
toolClasspath.from(antClasspath)
schema = layout.projectDirectory.file("src/main/schema/order.xsd")
packageName = "com.example.order"
outputDir = layout.buildDirectory.dir("generated/jaxb")
}
GenerateJaxbClasses is a cacheable task in buildSrc.
It writes a small Ant build file, then runs org.apache.tools.ant.Main with ExecOperations.javaexec().
The upgrade guide has the full class.
What about Gradle’s own Ant usage? #
Removing the Ant API in Gradle 10 doesn’t depend on any of Gradle’s internal Ant usage. Those are separate clean-up jobs, and they’ll land on their own schedule, outside of Gradle 10.
The biggest ones are the PMD, CodeNarc, and Checkstyle plugins, which run their tools through each tool’s Ant task:
| Plugin | Where it stands |
|---|---|
| PMD | #39470 runs PMD 7 and newer through the PMD Java API. Whether to deprecate PMD older than 7.0.0 isn’t decided yet. |
| CodeNarc | #39481 (draft) runs every CodeNarc version through CodeNarcRunner. |
| Checkstyle | Not started. Checkstyle documents its com.puppycrawl.tools.checkstyle classes for plugin authors, and its Ant task is a thin wrapper over them. |
We’ll cover anything that changes for you when those ship.
What’s next? #
Here’s what’s still on our list:
- Gradle 10: remove the deprecated Ant API and stop putting Ant on the build script classpath.
- Code quality plugins: move PMD, CodeNarc, and Checkstyle off their Ant tasks.
- Default excludes: replace Ant’s
DirectoryScannerexcludes. The replacement will almost certainly be configurable, at least for Gradle 10. gradle --version: stop printing the Ant line.- Longer term: download Ant in its own configuration, the way the code quality plugins already download their tools.
Does your build use Ant in a way this post doesn’t cover? We want to hear about it. Comment on #39501 or find us on the Gradle Community Slack.