Compiling Java with Ant
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Ant 1.10.18. Every example was run on JDK 26 on 2026-10-16 with the Ant 1.10.18
distribution unpacked in a scratch directory and the ant launch script on the PATH; the sources compile with
release="21", so the class files they produce target Java 21 even though the compiler is JDK 26. The sources are the
tidepool scenario used across this section: tidepool-model (records), tidepool-engine (reads data/stations.csv
with Commons CSV) and tidepool-cli (a picocli command, which exercises annotation processing). Paths in console output
were shortened to /work/.... Ant does not download libraries, so the Commons CSV, Commons IO, Commons Codec and picocli
JARs sit in a lib/ directory; the page on
Ivy Overview and the page on
Maven Resolver Ant Tasks show how to fetch
them from a repository instead.
Compiling is the first thing every Ant build does after cleaning, and the <javac> task is the most used task in the
tool. The page covers it in the order a build grows: a first compile, which Java release to compile for, which files Ant
decides to compile, the class path of a multi-module project, compiler selection and forking, warnings and failures,
annotation processors, modules, native headers, the dependency-checking <depend> task, the tasks that were removed from
the JDK (javah, rmic), native2ascii and JSP precompilation. The build files use targets, properties and paths that are
explained on Build File Structure,
Properties and
Paths, Filesets & Patterns.
A first compile
The smallest useful build file compiles one module. It sets every attribute that should never be left to a default:
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool-model" default="compile" basedir=".">
<property name="src.dir" location="tidepool-model/src/main/java"/>
<property name="classes.dir" location="build/model/classes"/>
<target name="compile" description="Compile the model library">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
release="21"
encoding="UTF-8"
debug="true"
debuglevel="lines,vars,source"
includeantruntime="false"
listfiles="true"/>
</target>
<target name="clean">
<delete dir="build"/>
</target>
</project>
Running ant compile twice shows the first idea of Ant compilation: the first run compiles the four files and lists them
(because of listfiles), the second finds the class files newer than the sources and does nothing.
$ ant compile
Buildfile: /work/ex1/build.xml
compile:
[mkdir] Created dir: /work/ex1/build/model/classes
[javac] Compiling 4 source files to /work/ex1/build/model/classes
[javac] /work/ex1/tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[javac] /work/ex1/tidepool-model/src/main/java/io/tidepool/model/Height.java
[javac] /work/ex1/tidepool-model/src/main/java/io/tidepool/model/Station.java
[javac] /work/ex1/tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant compile
Buildfile: /work/ex1/build.xml
compile:
BUILD SUCCESSFUL
Total time: 0 seconds
javap confirms what release and debug did to the class files: major version 65 is Java 21, and the SourceFile and
LineNumberTable attributes are present.
$ javap -v -cp build/model/classes io.tidepool.model.Height | grep -E "major|SourceFile"
major version: 65
SourceFile: "Height.java"
Source: Apache Ant manual: Javac task
What Ant decides to compile
<javac> is a directory-based task. It scans the source directory (the srcdir attribute or one or more nested <src>
elements), applies the includes, excludes and default excludes, and maps every X.java to destdir/X.class in the
same package directory. A source file whose class file is missing, or older than the source, goes on the list; everything
else is skipped. The list is then given to one javac invocation (in the same JVM unless fork is set), and javac may
compile further classes it finds through the source path, which Ant sets to the source directories by default.
The consequences are the same in every build:
-
Only names and timestamps are compared. Ant does not parse the sources, so it knows nothing about nested classes, classes whose name differs from the file name, or what a changed class means for its callers. That is the job of the
<depend>task, covered below. -
The source directory must be the root of the package tree. The manual warns that putting part of the package structure in
srcdirmakes Ant recompile on every run, because the class file is looked up at the wrong path. The experiment below pointssrcdirone level too deep and then, for contrast, restricts the right directory withincludes; the first build file compiles four files on each of two consecutive runs, the second compiles them once. -
Compile only part of the tree with
includesandexcludes, never with a longersrcdir.
<javac srcdir="tidepool-model/src/main/java/io/tidepool" destdir="build/classes"
release="21" includeantruntime="false"/>
$ ant compile
[javac] Compiling 4 source files to /work/ex5/build/classes
$ ant compile
[javac] Compiling 4 source files to /work/ex5/build/classes
<javac srcdir="tidepool-model/src/main/java" destdir="build/classes"
release="21" includeantruntime="false" includes="io/tidepool/model/**"/>
$ ant compile
[javac] Compiling 4 source files to /work/ex5/build/classes
$ ant compile
(no javac line: every class file is up to date)
The javac task, attribute by attribute
The tables list every attribute of the 1.10.18 <javac> task; the "since" column names the release only where the manual
gives one. Attributes that take a path (classpath, sourcepath, bootclasspath, extdirs, modulepath,
modulesourcepath, upgrademodulepath and srcdir) can also be written as nested <classpath>, <sourcepath>,
<bootclasspath>, <extdirs>, <modulepath>, <modulesourcepath>, <upgrademodulepath> and <src> elements (note that
srcdir becomes <src>), and each has a ...ref variant that takes the id of a path defined elsewhere.
| Attribute | Meaning | Default |
|---|---|---|
|
Location of the Java files. Required unless nested |
none |
|
Where the class files go; Ant creates the package directories below it. |
none (classes next to the sources) |
|
Pattern selection of the files to compile; same syntax as any directory-based task. |
all |
|
Where |
the |
|
Encoding of the source files. |
the platform default of the JVM running Ant |
| Attribute | Meaning | Default |
|---|---|---|
|
The class path used to compile. Entries that point to non-existent files or directories are dropped. |
none |
|
Boot class path and extension directories; ignored when |
none |
|
Add the Ant libraries to the class path. Set it to |
|
|
Add the default run-time libraries of the executing JVM to the class path. |
|
|
Put the destination directory on the class path (since Ant 1.7.1). Set it to |
|
| Attribute | Meaning | Default |
|---|---|---|
|
The value of the |
none; ignored on JDK 8 or earlier |
|
The |
none; the compiler’s own default, which changes with the JDK |
|
The |
none; depends on the JDK and on |
| Attribute | Meaning | Default |
|---|---|---|
|
Compile with debug information. Off passes |
|
|
The keywords appended to |
nothing appended |
|
Pass |
|
|
Ignored by modern |
|
|
List the source files to be compiled. |
|
|
Fail the build on a compile error. |
|
|
Properties set to |
unset |
|
Dependency tracking for the |
unset |
|
Since Ant 1.8 Ant creates an empty |
|
| Attribute | Meaning | Default |
|---|---|---|
|
The compiler implementation; the values are in the next section. |
the |
|
Run |
|
|
Full path of the |
the compiler of the current JDK |
|
Memory settings ( |
the JVM defaults |
|
Where Ant puts temporary files when the forked command line exceeds 4 kB. |
|
| Attribute | Meaning | Since |
|---|---|---|
|
Where to find the application modules: directories of modules, module files or exploded modules. |
Ant 1.9.7 |
|
Input sources for a multiple-module compilation; replaces |
Ant 1.9.7 |
|
Modules that replace upgradeable modules in the runtime image. |
Ant 1.9.7 |
|
Where |
Ant 1.9.8 |
The task also accepts nested <include>, <exclude> and <patternset> elements, nested <compilerarg> elements for
extra compiler switches, <compilerclasspath> for loading a custom compiler adapter, and a nested element of any defined
type that implements CompilerAdapter. There is no proc, parameters or lint attribute: those switches go through
<compilerarg>, as the sections below show.
Source: Apache Ant manual: Javac task
release versus source and target
source and target are the historical pair of switches, and they have a trap: the compiler of the JDK that runs Ant
compiles against its own API, not against the API of the release named. A class compiled with source="21" target="21" on
JDK 26 can call a method that exists only in JDK 26 and fail at run time on Java 21. release closes the trap because
javac --release also restricts the compiled API to that of the chosen release. javac says so itself when the old pair is
used:
<javac srcdir="tidepool-model/src/main/java" destdir="build/classes"
source="21" target="21" includeantruntime="false"/>
[javac] Compiling 4 source files to /work/ex2/build/classes
[javac] warning: [options] location of system modules is not set in conjunction with -source 21
[javac] not setting the location of system modules may lead to class files that cannot run on JDK 21
[javac] --release 21 is recommended instead of -source 21 -target 21 because it sets the location of system modules automatically
[javac] 1 warning
With release set, source and target are not used at all, and Ant reports it:
<javac srcdir="tidepool-model/src/main/java" destdir="build/classes"
release="21" source="11" target="11" includeantruntime="false"/>
[javac] Compiling 4 source files to /work/ex2/build/classes
[javac] Ignoring source, target and bootclasspath as release has been set
The JDK itself decides which releases --release accepts. JDK 26 no longer supports 7, and 8 still works but is obsolete;
compiling the records of tidepool for 8 also fails because records need Java 16:
[javac] error: release version 7 not supported
[javac] Usage: javac <options> <source files>
[javac] use --help for a list of possible options
[javac] warning: [options] source value 8 is obsolete and will be removed in a future release
[javac] warning: [options] target value 8 is obsolete and will be removed in a future release
[javac] warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
[javac] /work/ex2/tidepool-model/src/main/java/io/tidepool/model/Constituent.java:4: error: records are not supported in -source 8
[javac] public record Constituent(String name, double amplitude, double phase) {
[javac] ^
[javac] (use -source 16 or higher to enable records)
Some build files cannot be edited, for example those inherited from an old project. For them Ant offers two properties that
supply defaults for the source and target attributes when the attributes are not set (the properties are ignored when the
attributes are present): ant.build.javac.source and ant.build.javac.target, both since Ant 1.7. They are passed with -D
or set in a properties file, and they do not fix the trap above, because they feed -source and -target, not --release.
$ ant compile -Dant.build.javac.source=21 -Dant.build.javac.target=21
[javac] Compiling 1 source file to /work/ex10/build/classes
[javac] warning: [options] location of system modules is not set in conjunction with -source 21
...
release needs Ant 1.9.8 or later and JDK 9 or later to run. In a Maven build the same decision is
maven.compiler.release (Compiling & Toolchains);
in Gradle it is options.release or a toolchain
(Java Projects,
Toolchains).
Source: JDK 26 javac: --release
includeantruntime
By default <javac> puts the Ant libraries on the class path of the compilation, so that a build file that compiles custom
tasks does not need to name ant.jar. It also makes the result depend on which Ant is installed. Ant 1.10.18 warns when
the attribute is left out:
<javac srcdir="tidepool-model/src/main/java" destdir="build/classes"/>
[javac] /work/ex2/build.xml:4: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 4 source files to /work/ex2/build/classes
Set
includeantruntime="false" on every <javac>. The one case that needs true is a task that compiles a class that extends
Ant classes, for example a custom task such as the <checkstations> task of
Writing Custom Tasks; there, name the Ant jar on
the class path instead (${ant.core.lib} is the built-in property that points at it). A build file can also set the
build.sysclasspath property, which decides where the Ant run-time class path goes: last, the default the warning
names, appends it after the build’s own class path, first puts it before, only uses it alone, and ignore leaves it
out.
Source: Apache Ant manual: Javac task and build.sysclasspath
A multi-module compile: class paths between modules
tidepool has three modules, and each depends on the previous one. Ant has no project model, so the dependency between
modules is a class path pointing at the previous module’s output directory, and the order is the order of the targets'
depends. The same file shows the nested <compilerarg> and a processor path for the picocli annotation processor
(explained in the next sections):
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool" default="compile" basedir=".">
<property name="java.release" value="21"/>
<property name="build.dir" location="build"/>
<path id="processor.path">
<fileset dir="lib" includes="picocli-4.7.7.jar, picocli-codegen-4.7.7.jar"/>
</path>
<target name="compile-model">
<mkdir dir="${build.dir}/model/classes"/>
<javac srcdir="tidepool-model/src/main/java"
destdir="${build.dir}/model/classes"
release="${java.release}" encoding="UTF-8" debug="true"
includeantruntime="false"/>
</target>
<path id="engine.classpath">
<pathelement location="${build.dir}/model/classes"/>
<pathelement location="lib/commons-csv-1.14.1.jar"/>
<pathelement location="lib/commons-io-2.20.0.jar"/>
<pathelement location="lib/commons-codec-1.19.0.jar"/>
</path>
<target name="compile-engine" depends="compile-model">
<mkdir dir="${build.dir}/engine/classes"/>
<javac srcdir="tidepool-engine/src/main/java"
destdir="${build.dir}/engine/classes"
classpathref="engine.classpath"
release="${java.release}" encoding="UTF-8" debug="true"
includeantruntime="false">
<compilerarg value="-Xlint:all"/>
</javac>
</target>
<path id="cli.classpath">
<path refid="engine.classpath"/>
<pathelement location="${build.dir}/engine/classes"/>
<pathelement location="lib/picocli-4.7.7.jar"/>
</path>
<target name="compile-cli" depends="compile-engine">
<mkdir dir="${build.dir}/cli/classes"/>
<javac srcdir="tidepool-cli/src/main/java"
destdir="${build.dir}/cli/classes"
classpathref="cli.classpath"
release="${java.release}" encoding="UTF-8" debug="true"
includeantruntime="false">
<compilerarg value="-processorpath"/>
<compilerarg pathref="processor.path"/>
<compilerarg value="-Aproject=io.tidepool/tidepool-cli"/>
</javac>
</target>
<target name="compile" depends="compile-cli"/>
</project>
$ ant
Buildfile: /work/tidepool/build.xml
compile-model:
[mkdir] Created dir: /work/tidepool/build/model/classes
[javac] Compiling 4 source files to /work/tidepool/build/model/classes
compile-engine:
[mkdir] Created dir: /work/tidepool/build/engine/classes
[javac] Compiling 2 source files to /work/tidepool/build/engine/classes
compile-cli:
[mkdir] Created dir: /work/tidepool/build/cli/classes
[javac] Compiling 1 source file to /work/tidepool/build/cli/classes
compile:
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant
Buildfile: /work/tidepool/build.xml
compile-model:
compile-engine:
compile-cli:
compile:
BUILD SUCCESSFUL
Total time: 0 seconds
Three things in this build file are classic sources of bugs:
-
Missing transitive libraries. Commons CSV needs Commons IO and Commons Codec at run time. Compiling does not need them, so
javacis happy withcommons-csv-1.14.1.jaralone, and the build breaks only when the program runs, withNoClassDefFoundError: org/apache/commons/io/output/AppendableOutputStream. Ant does not read the POM of a dependency; a tool that does (Ivy, or the Maven Resolver Ant Tasks) is the answer, and the Maven and Gradle equivalents resolve transitive dependencies themselves (Dependency Management Fundamentals). -
Output directories on the class path.
${build.dir}/model/classesis a directory, not a JAR, and it exists only after thecompile-modeltarget ran. Ant silently drops non-existent entries from the class path of<javac>, so a build file with a wrong directory name compiles with a shorter class path and fails withpackage io.tidepool.model does not exist(andpackage org.apache.commons.csv does not exist, if a JAR is missing too) instead of a message about the path. -
One
lib/directory for everything. Afilesetoverlib/*.jarputs both compile-time and run-time libraries, and processors, on every class path. Separate paths by role (compile, processor, test, run) as the example does; Maven scopes and Gradle configurations give the same separation by name.
Source: Apache Ant manual: Javac task
Choosing the compiler: the compiler attribute and build.compiler
The compiler attribute (or the build.compiler property, which affects every <javac> in the build) selects the
implementation of the CompilerAdapter interface that builds the command line. The values the manual lists:
| Value | Compiler |
|---|---|
|
The compiler of JDK 1.1 and 1.2. |
|
The standard compiler of JDK 1.3 and later. The aliases |
|
The Jikes compiler (the Jikes notes in the manual describe extra properties). |
|
The command-line compiler of Microsoft’s SDK for Java / Visual J++. |
|
The Kopi compiler. |
|
The compiler from GCC. |
|
The Symantec Java compiler. |
|
Run either the modern or the classic compiler in a JVM of its own. |
a class name |
A class that implements |
With nothing set, the default is the javacX adapter that matches the JDK running Ant, which is why release="21" works on
JDK 26 without a compiler attribute. The old values of the table
(jikes, jvc, kjc, gcj, sj, classic) are listed for completeness; their compilers are not maintained, and no
current build needs them. A value that is neither a known name nor a class fails with a message about the class:
BUILD FAILED
/work/ex5/build.xml:4: Class not found: nonsense
javac10+ and extJavac both worked on this installation; the second one starts a separate JVM, as the manual says, and the
build output is the same.
The compiler attribute is also how the command-line switches are chosen when fork is set: compiler="javac1.1" with a
forked JDK 1.1 javac makes Ant write -depend instead of -Xdepend. Today the useful part is the reverse, fork.
Forking and choosing a JDK
By default <javac> calls the compiler API inside the JVM that runs Ant. fork="true" starts the javac executable of
the JDK instead, or the one named by executable. Forking is the way to compile with a JDK other than the one running Ant,
to give the compiler more memory (memoryMaximumSize), and to avoid the file locking the manual describes for Windows (an
unforked modern compiler keeps the class path JARs open, so a later delete or move of them fails). The -v flag shows the
command that is built; the arguments below also show the defaults Ant chooses: the class path is the output directory, the
source path is the srcdir, and -g:none appears because debug is off.
<javac srcdir="tidepool-model/src/main/java" destdir="build/classes"
release="21" includeantruntime="false" fork="true"/>
$ ant -v compile
...
compile:
[javac] io/tidepool/model/Constituent.java added as io/tidepool/model/Constituent.class doesn't exist.
[javac] io/tidepool/model/Height.java added as io/tidepool/model/Height.class doesn't exist.
[javac] io/tidepool/model/Station.java added as io/tidepool/model/Station.class doesn't exist.
[javac] io/tidepool/model/TideEvent.java added as io/tidepool/model/TideEvent.class doesn't exist.
[javac] Compiling 4 source files to /work/ex5/build/classes
[javac] Using external javac compiler
[javac] Compilation arguments:
[javac] '-d'
[javac] '/work/ex5/build/classes'
[javac] '-classpath'
[javac] '/work/ex5/build/classes'
[javac] '-sourcepath'
[javac] '/work/ex5/tidepool-model/src/main/java'
[javac] '-g:none'
[javac] '--release'
[javac] '21'
The first four lines also explain why a file was compiled: with -v, Ant says which class file did not exist or was older.
A wrong executable fails early with a short message:
BUILD FAILED
/work/ex5/build.xml:4: Error running /opt/none/bin/javac compiler
When the compile is expected to use another installed JDK, prefer a build property such as jdk.home (set from the
JAVA_HOME environment variable or from a properties file) over a hard-coded path, and read the build’s JDK from ant
-version and ant -diagnostics
(Running Ant). In Maven and Gradle the same need is met by
toolchains, which select the JDK by version and vendor instead of by path.
Source: Apache Ant manual: Javac task
Warnings, lint and failures
Without deprecation, javac prints only two summary lines for a deprecated API. The deprecation attribute and the
-Xlint switches give the details. The next experiments compile a class that calls new Date(126, 0, 1):
[javac] Note: /work/ex10/src/Old.java uses or overrides a deprecated API.
[javac] Note: Recompile with -Xlint:deprecation for details.
<javac srcdir="src" destdir="build/classes2" release="21" deprecation="true" includeantruntime="false"/>
[javac] /work/ex10/src/Old.java:5: warning: [deprecation] Date(int,int,int) in Date has been deprecated
[javac] Date d = new Date(126, 0, 1);
[javac] ^
[javac] 1 warning
Lint categories and "warnings are errors" go through <compilerarg>; the -Werror switch turns the build into a gate:
<javac srcdir="src" destdir="build/classes3" release="21" includeantruntime="false">
<compilerarg value="-Xlint:all"/>
<compilerarg value="-Werror"/>
</javac>
[javac] /work/ex10/src/Old.java:5: warning: [deprecation] Date(int,int,int) in Date has been deprecated
[javac] Date d = new Date(126, 0, 1);
[javac] ^
[javac] error: warnings found and -Werror specified
[javac] 1 error
[javac] 1 warning
BUILD FAILED
/work/ex10/build.xml:12: Compile failed; see the compiler error output for details.
A compile error fails the build with Compile failed; see the compiler error output for details., because failonerror
defaults to true. Set failonerror="false" together with errorProperty when the build must go on, for example to
collect all failures before reporting; updatedProperty is set only when a compile took place and succeeded, which is why
it stays unset below:
<javac srcdir="broken" destdir="build/classes" release="21" includeantruntime="false"
failonerror="false" errorProperty="compile.failed" updatedProperty="compile.updated"/>
<echo>failed=${compile.failed} updated=${compile.updated}</echo>
[javac] Compiling 1 source file to /work/ex5/build/classes
[javac] /work/ex5/broken/Bad.java:1: error: incompatible types: String cannot be converted to int
[javac] public class Bad { int x = "text"; }
[javac] ^
[javac] 1 error
[javac] Compile failed; see the compiler error output for details.
[echo] failed=true updated=${compile.updated}
Source: Apache Ant manual: Javac task
Annotation processors
Annotation processors need three decisions in an Ant build: where the processor JARs are, whether processing is switched on,
and what options it receives. The <javac> task has no attribute for them; all three go through <compilerarg>.
The tidepool CLI uses picocli-codegen, a processor that writes GraalVM native-image configuration for the @Command
class. The build file above passes it a processor path and the project option the processor requires:
<compilerarg value="-processorpath"/>
<compilerarg pathref="processor.path"/>
<compilerarg value="-Aproject=io.tidepool/tidepool-cli"/>
Two nested elements are needed for a path, because -processorpath and its value are separate arguments; pathref converts
a <path> to the platform’s path string. After the build the processor’s output is next to the class files:
$ find build/cli -type f | sort
build/cli/classes/META-INF/native-image/picocli-generated/io.tidepool/tidepool-cli/proxy-config.json
build/cli/classes/META-INF/native-image/picocli-generated/io.tidepool/tidepool-cli/reflect-config.json
build/cli/classes/META-INF/native-image/picocli-generated/io.tidepool/tidepool-cli/resource-config.json
build/cli/classes/io/tidepool/cli/Main.class
A processor that is only on the compile class path is a trap on a recent JDK. Since JDK 23 javac no longer runs processors
that it discovers on the class path unless processing is requested explicitly: the JDK 26 javac manual says that without -proc annotation
processing happens only if another option configures it (such as -processorpath or -processor). The experiment compiles
all seven tidepool sources with picocli-codegen on the class path and counts the files below META-INF:
Extra compilerarg |
Files below META-INF in the output |
|---|---|
|
0 |
|
3 |
|
0 |
Compilation succeeded in all three runs and printed nothing about the skipped processor, so the failure shows up later, as
a missing generated class or a missing configuration file. The fix is either a processor path (preferred: it keeps processors
off the application class path) or -proc:full. -proc:only runs processors without compiling, which is useful to generate
sources into a directory with -s before a second compile. For the processor itself, picocli documents the project option
and how to enable the processor.
The equivalents are annotationProcessorPaths in the Maven compiler plugin
(Compiling & Toolchains) and the
annotationProcessor configuration in Gradle
(Java Projects); both keep processors off the compile
class path by construction.
Source: picocli: Annotation Processor
Modules: modulepath and modulesourcepath
Ant supports the Java module system from 1.9.7. There are two shapes.
A single module. A module-info.java at the root of the source directory is compiled like any other file; add
modulepath when it requires modules that are not in the JDK. This build compiles tidepool-model as the module
io.tidepool.model:
module io.tidepool.model {
exports io.tidepool.model;
}
<javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
$ ant compile
[javac] Compiling 5 source files to /work/ex6b/build/classes
$ ls build/classes
io
module-info.class
Several modules in one go. With modulesourcepath, one <javac> compiles a set of modules from a layout of one directory
per module, and writes one output directory per module under destdir. The * token in the manual’s example stands for the
module name. Here io.tidepool.engine requires io.tidepool.model (transitively) and org.apache.commons.csv, and the
CSV JAR goes on the modulepath:
module io.tidepool.engine {
requires transitive io.tidepool.model;
requires org.apache.commons.csv;
exports io.tidepool.engine;
}
<project name="tidepool-modules" default="compile" basedir=".">
<target name="compile">
<mkdir dir="build/mods"/>
<javac modulesourcepath="src"
destdir="build/mods"
release="21"
includeantruntime="false">
<modulepath>
<fileset dir="lib" includes="*.jar"/>
</modulepath>
</javac>
</target>
</project>
$ ant compile
compile:
[mkdir] Created dir: /work/ex6/build/mods
[javac] Compiling 8 source files to /work/ex6/build/mods
BUILD SUCCESSFUL
$ find build -type f | sort
build/mods/io.tidepool.engine/io/tidepool/engine/StationReader.class
build/mods/io.tidepool.engine/io/tidepool/engine/TideEngine.class
build/mods/io.tidepool.engine/module-info.class
build/mods/io.tidepool.model/io/tidepool/model/Constituent.class
build/mods/io.tidepool.model/io/tidepool/model/Height.class
build/mods/io.tidepool.model/io/tidepool/model/Station.class
build/mods/io.tidepool.model/io/tidepool/model/TideEvent.class
build/mods/io.tidepool.model/module-info.class
$ java --module-path build/mods:lib --describe-module io.tidepool.engine
exports io.tidepool.engine
requires io.tidepool.model transitive
requires java.base mandated
requires org.apache.commons.csv
Without the modulepath, the compile fails at the requires line:
[javac] /work/ex6/src/io.tidepool.engine/module-info.java:3: error: module not found: org.apache.commons.csv
[javac] requires org.apache.commons.csv;
[javac] ^
[javac] 1 error
BUILD FAILED
/work/ex6/build.xml:4: Compile failed; see the compiler error output for details.
Two further points. A JAR on the module path becomes a named module (an automatic module if it has no module-info), while
the same JAR on the classpath is in the unnamed module and invisible to requires; an Ant build that mixes both must keep
the paths apart. And upgrademodulepath is for the rare case of replacing an upgradeable JDK module. Packaging the compiled
modules into a modular JAR or a runtime image is on
Packaging Archives.
Native headers: nativeheaderdir replaces javah
A class with a native method needs a C header with the JNI function prototype. javac -h generates it while compiling, and
the Ant attribute nativeheaderdir (Ant 1.9.8) passes the directory:
package io.tidepool.jni;
public class Harmonics {
public static final int MAX_CONSTITUENTS = 37;
public static native double predict(double[] amplitudes, double[] phases, long epochSecond);
}
<javac srcdir="src" destdir="build/classes" nativeheaderdir="build/headers"
release="21" includeantruntime="false"/>
$ ls build/headers
io_tidepool_jni_Harmonics.h
$ cat build/headers/io_tidepool_jni_Harmonics.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class io_tidepool_jni_Harmonics */
#ifndef _Included_io_tidepool_jni_Harmonics
#define _Included_io_tidepool_jni_Harmonics
#ifdef __cplusplus
extern "C" {
#endif
#undef io_tidepool_jni_Harmonics_MAX_CONSTITUENTS
#define io_tidepool_jni_Harmonics_MAX_CONSTITUENTS 37L
/*
* Class: io_tidepool_jni_Harmonics
* Method: predict
* Signature: ([D[DJ)D
*/
JNIEXPORT jdouble JNICALL Java_io_tidepool_jni_Harmonics_predict
(JNIEnv *, jclass, jdoubleArray, jdoubleArray, jlong);
#ifdef __cplusplus
}
#endif
#endif
The header is written only when the class is compiled. Ant decides what to compile from class file timestamps and does not
look at native headers, so a deleted header is not regenerated while the class file is newer than the source; delete the
class (or the whole output directory) to force it. The header above also contains the MAX_CONSTITUENTS constant: javac -h
writes the constants of the class as #define lines.
The older <javah> task generated the same headers from compiled classes. It was deprecated with Java 9 and removed from the
JDK in Java 10, and Ant 1.10.2 made it fail with an explicit message rather than a missing executable:
<javah class="io.tidepool.jni.Harmonics" destdir="build/headers2">
<classpath path="build/classes"/>
</javah>
BUILD FAILED
/work/ex7/build.javah.xml:3: javah does not exist under Java 10 and higher, use the javac task with nativeHeaderDir instead
<rmic> has the same history: rmic was deprecated in Java 13 and removed in Java 15, and the Ant task fails on Java 15
and later unless you give it an explicit executable of an older JDK:
<rmic base="build/classes" classname="io.tidepool.jni.Harmonics"/>
[rmic] RMI Compiling 1 class to /work/ex7/build/classes
BUILD FAILED
/work/ex7/build.rmic.xml:3: rmic does not exist under Java 15 and higher, use rmic of an older JDK and explicitly set the executable attribute
Modern RMI needs no stubs (dynamic proxies replace them since Java 5), so the answer for a build is to delete the <rmic>
call. The -iiop and -idl options (CORBA) fail from Java 11, because that release removed the CORBA packages; the
manual states that Ant 1.10.3 and later fail on them.
Source: Apache Ant manual: Javah task
Source: Apache Ant manual: Rmic task
Checking dependencies with the depend task
Timestamps catch a changed source but not what it means for its callers. Suppose Client calls Api.twice(int), and Api
is changed to return a String. Only Api.java is newer, so a plain <javac> recompiles only that file, Client.class
stays, and the build succeeds. The failure comes at run time:
package demo;
public class Api {
public static String twice(int x) {
return "" + 2 * x;
}
}
package demo;
public class Client {
public static void main(String[] args) {
int result = Api.twice(21);
System.out.println("twice(21) = " + result);
}
}
$ ant -f build.nodepend.xml compile
compile:
[javac] Compiling 1 source file to /work/ex8/build/classes
BUILD SUCCESSFUL
$ java -cp build/classes demo.Client
Exception in thread "main" java.lang.NoSuchMethodError: 'int demo.Api.twice(int)'
at demo.Client.main(Unknown Source)
The <depend> task reads the class references encoded in the existing class files (it does not parse the sources), builds
the inverse "affected by" relation, and deletes the class files of every class that depends on a class that is out of date.
The following <javac> then recompiles them, and the compiler reports the real problem. cache stores the dependency
information between runs, and closure="true" follows indirect dependencies too (A depends on B depends on C):
<project name="depend-demo" default="compile" basedir=".">
<target name="compile">
<mkdir dir="build/classes"/>
<depend srcdir="src" destdir="build/classes" cache="build/depcache" closure="true"/>
<javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
</target>
</project>
$ ant compile
compile:
[depend] Deleted 2 out of date files in 0 seconds
[javac] Compiling 2 source files to /work/ex8/build/classes
[javac] /work/ex8/src/demo/Client.java:5: error: incompatible types: String cannot be converted to int
[javac] int result = Api.twice(21);
[javac] ^
[javac] 1 error
BUILD FAILED
/work/ex8/build.xml:5: Compile failed; see the compiler error output for details.
The attributes of <depend> are srcdir (required, a path of source directories), destdir (the class files to analyse;
defaults to srcdir), cache, closure, dump (write the dependencies to the debug log), classpath (jars and classes to
also check against, which should not contain JARs that never change, such as the JDK or third-party libraries) and
warnOnRmiStubs. It forms an implicit fileset, so <include> and <exclude> work as in <javac>. The task is part of
ant.jar.
Its limits matter. It cannot see a class relationship that the compiler removed, such as the inlined value of a
public static final constant (changing DEBUG in a Constants class is not picked up by the classes that read it), it
cannot relate a non-public class to a source file, and it costs time on large trees. A clean build on CI, or the incremental
compilation of Maven and Gradle, is the more robust answer; Gradle tracks class-level dependencies by itself, and the Maven
compiler plugin recompiles the module when it detects a change. See
Build Automation Fundamentals for the general
picture. The depend attribute of <javac> is not a substitute: the manual says it only works for the jikes and
classic compilers.
Source: Apache Ant manual: Depend task
native2ascii
Property resource bundles were ISO-8859-1 only until Java 9 (which made them UTF-8, JEP 226), so files kept in another
encoding had to be converted with the JDK tool native2ascii. That tool is gone from the JDK: JDK 26 has no native2ascii
executable. Ant’s task works anyway because, since Ant 1.9.8, the builtin converter is
Ant’s own implementation. It converts a directory of files from a native encoding to ASCII with \uXXXX escapes (the
reverse attribute converts back, supported by the sun and builtin converters):
station.name=Île de Bréhat
station.country=França
<native2ascii src="i18n" dest="build/i18n" encoding="UTF-8" includes="**/*.properties"/>
convert:
[native2ascii] Converting 1 file from /work/ex9/i18n to /work/ex9/build/i18n
BUILD SUCCESSFUL
$ cat build/i18n/messages.properties
station.name=Île de Bréhat
station.country=França
The reverse="true" run on build/i18n with the same encoding gave back the original text. The attributes are src
(default basedir), dest (required), encoding, ext (rename the output files), reverse, implementation
(default, sun, kaffe or builtin), the usual includes/excludes set and an optional nested <mapper>; if src
and dest are the same directory, ext or a mapper is required. Use it when a legacy tool chain needs pure-ASCII
properties files; for new code keep the files in UTF-8.
JSP precompilation
Compiling JSP pages is a different compile with different tools. The core Ant <jspc> task is deprecated: the manual says
it was tested only against Tomcat 4.x, has known problems with Tomcat 5.x that will not be fixed, and recommends the task
that Tomcat ships instead. The Jasper JspC task, its Ant integration and the production setting are in
Jasper Configuration & Precompilation.
Source: Apache Ant manual: Jspc task
The equivalents in Maven and Gradle
Ant <javac> |
Maven (compiler plugin 3.16.0) | Gradle 9.8.1 |
|---|---|---|
|
|
|
|
|
|
|
dependencies and their scopes |
dependency configurations ( |
|
|
|
|
|
|
|
the plugin builds the module path from |
module path inference for a |
|
|
|
|
|
|
|
incremental detection by the plugin |
incremental compilation with class-level tracking |
More on the other two tools is on Compiling & Toolchains and Java Projects; the side-by-side verdict is on Comparing Ant, Maven & Gradle.
Where the book is outdated
Java Development with Ant (Ant 1.5, 2002) compiles with <javac srcdir destdir classpath> and does not mention
includeantruntime, release, modulepath or nativeheaderdir, which came with Ant 1.8, 1.9.8 and 1.9.7. Its JNI chapter
relies on <javah>, which fails on Java 10 and later (use nativeheaderdir); its RMI material relies on <rmic>, which
fails on Java 15 and later; its source="1.4" examples fail on a current compiler, which rejects releases older than 8
(and flags 8 as obsolete); its JSP precompilation with <jspc> is Tomcat-4-only and deprecated; and the jikes compiler it
recommends for speed is no longer maintained. The section’s bibliography
(Bibliography) gives the publisher page of the book.
Rewriting such a build is covered on Deprecations & Migration.