Custom Ant Tasks for Code Generation in NetBeans

NetBeans can turn a small Java class into an Apache Ant task, bringing repeatable source generation into the IDE build instead of leaving it to a separate script. It can create DTOs, API models or metadata without leaving NetBeans. This approach builds a custom Ant task for code generation without a plugin or separate application.

A useful generator has a narrow job: read a template, replace a few placeholders and write a predictable file. Australian teams often call this “codegen” in stand-ups, so a clear task name and log message prevent confusion about which generator ran. The example below uses a Java Class Library for the task and a small Ant build file.

Prepare the NetBeans project

Create a Java Class Library project named ant-codegen and place the implementation in com.example.codegen. Add the Ant library through Properties > Libraries, using the same Ant distribution as your build server. The compiler needs ant.jar because the task extends org.apache.tools.ant.Task.

Keep templates in src/org/resources and generated files in generated-sources. Use Australian English in comments and logs, including “optimise” and “artefact”; that sounds natural to Melbourne and Brisbane teams. If a template is XML, the advice to format XML and HTML keeps it readable before the generator reads it.

Implement the Ant task in Java

Extend Task and expose attributes through setter methods. Ant maps template, outputDir and packageName to the class below, which reads UTF-8 text, replaces {{package}}, creates the destination folder and reports through Ant’s logging system.

package com.example.codegen;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

public class GenerateModel extends Task {
    private File template;
    private File outputDirectory;
    private String packageName;

    public void setTemplate(File template) {
        this.template = template;
    }

    public void setOutputDir(File outputDirectory) {
        this.outputDirectory = outputDirectory;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    @Override
    public void execute() throws BuildException {
        if (template == null || outputDirectory == null || packageName == null) {
            throw new BuildException(
                    "template, outputDir and packageName are required");
        }

        try {
            String source = Files.readString(
                    template.toPath(), StandardCharsets.UTF_8)
                    .replace("{{package}}", packageName);

            String packagePath = packageName.replace('.', File.separatorChar);
            Path destination = outputDirectory.toPath()
                    .resolve(packagePath)
                    .resolve("GeneratedModel.java");

            Files.createDirectories(destination.getParent());
            Files.writeString(destination, source, StandardCharsets.UTF_8);
            log("Generated model source at " + destination);
        } catch (IOException ex) {
            throw new BuildException("Could not generate the model source", ex);
        }
    }
}

Compile the library before running the build file because Ant loads the compiled class. The implementation targets Java 11 or later. A richer generator can introduce a template engine later without changing how developers invoke the task.

The template can remain deliberately small:

package {{package}}

public final class GeneratedModel {
    public String getStatus() {
        return "ready";
    }
}

Define and run the Ant build

Add build.xml to the project root. The taskdef element loads the compiled class and gives it the Ant element name generateModel. NetBeans Java projects normally use nbproject/build-impl.xml, so use the IDE’s Run Target command on build.xml, rather than assuming the file replaces the standard build.

<project name="model-generator" default="generate-model" basedir=".">
    <taskdef name="generateModel"
             classname="com.example.codegen.GenerateModel"
             classpath="build/classes"/>

    <target name="generate-model">
        <generateModel template="src/org/resources/model.java.template"
                       outputDir="generated-sources"
                       packageName="com.example.model"/>
    </target>
</project>

After building the task library, right-click build.xml and run generate-model, or let the default target run it. In the standard NetBeans layout, compiled task classes are under build/classes, which explains the classpath above. A ClassNotFoundException usually means the library has not been compiled or the classpath points at the wrong directory.

Add generated sources to the build

Add generated-sources as a source root through Project Properties > Sources. NetBeans can then compile the generated Java and offer the same navigation and refactoring as hand-written code. Create the folder before adding it if your NetBeans release will not accept a missing directory.

For web projects, generated classes can supply request and response types for a JAX-RS Jersey service. Keep the task independent of the web layer. Australian billing templates may need AUD and GST metadata, but those values should be build properties rather than assumptions embedded in a generic generator.

Make generation part of the compile sequence for a single Clean and Build action, using the build script’s -pre-compile hook. Otherwise, run the Ant target before compilation in both the IDE and CI, so the compiler never uses stale classes.

Diagnose failures and build delays

Use the Ant output window to separate task failures from NetBeans compilation errors. A missing attribute produces the task’s BuildException; a classpath problem produces ClassNotFoundException; an unrecognised package usually means the source root was not registered. Clean and rebuild the task library whenever its API changes.

On a large repository, optimise NetBeans performance before changing the generator, since indexing and background scanning can dominate the wait. A run in Brisbane and another in Perth can occur in different time zones, so record timestamps in UTC and label local displays with the relevant Australian time zone.

Apply practical safeguards

Treat generated source as a build artefact, not a file for manual editing. Document the regeneration command and exclude generated output from version control when the build always recreates it. For a Canberra consultancy, reproducible artefacts also make client handovers easier to audit.

The Python health daemon is a useful parallel: it separates collecting system metrics from sending them on a schedule, just as your Ant task should separate generating source from using it.

  • Validate required attributes and resolve every output path beneath the intended build directory.
  • Use UTF-8 and consistent line endings so files compare cleanly across developer machines.
  • Write a file only when its contents change, avoiding needless rebuilds and noisy diffs.
  • Keep the task source in version control and test it against the same Ant version used in CI.
  • Run the generator twice and compare outputs to catch non-deterministic content.
  • Compile the generated sources in a clean build before publishing an artefact.