Build Tools: Gradle, Maven and 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 Groovy 6.0 (6.0.0, 2026-09-22). Compiling Groovy is done by the compiler classes in the groovy jar, and every build tool below is a thin way of calling them with the right classpath, source directories and options. The Ant section was run: Groovy 6.0.0 on JDK 26 (checked on 2026-10-14, on macOS), using the Ant 1.10.18 jars that ship in the distribution’s lib directory, since no separate Ant installation exists on that machine. The Gradle and Maven snippets are not run here: no Gradle was available and the Maven build was not executed, so treat their plugin and version numbers as the shape of a working configuration to be checked against the current releases, not as verified output. The depth for the Gradle Groovy DSL itself (build scripts, tasks, the Gradle API) is the subject of issue #300; this page only covers how Gradle builds Groovy code. For the Java side of the same tools see Java build and tooling.

The compiler options a build tool has to set

Whatever the tool, a Groovy build sets the same few things:

  • the Groovy library on the compile classpath (org.apache.groovy:groovy, the groups changed from org.codehaus.groovy with Groovy 4.0), plus any optional module such as groovy-json or groovy-xml that the code imports;

  • the source directories (conventionally src/main/groovy and src/test/groovy);

  • the file encoding and the target bytecode level, which follow the JDK in use (Groovy 6.0 needs JDK 17 or newer);

  • when Groovy and Java sources reference each other, joint compilation, described next.

Joint compilation

Groovy and Java classes can depend on each other. The Java compiler cannot read .groovy files, so the build first asks the Groovy compiler to generate Java stubs (declarations without bodies) for the Groovy classes, compiles the Java sources against those stubs, and finally compiles the Groovy sources against the real Java classes. Gradle does this automatically when Java files sit in the Groovy source set; Maven with GMavenPlus uses the generateStubs goal; the Ant <groovyc> task does it when it contains a nested <javac>. The Groovy 6.0 release notes add that AST-generated members now appear in the stubs, so Java code can call, for example, a getter produced by @Canonical or @Immutable.

Gradle

The groovy plugin adds a Groovy source set on top of the java plugin: compilation tasks compileGroovy and compileTestGroovy, a groovydoc task, and the directories src/main/groovy and src/test/groovy. Groovy source files are also looked up for Java classes in the same directory, which is how joint compilation is enabled. A minimal Groovy DSL script (build.gradle), not run here:

plugins {
    id 'groovy'
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.groovy:groovy:6.0.0'
    implementation 'org.apache.groovy:groovy-json:6.0.0'

    testImplementation 'org.spockframework:spock-core:2.5-groovy-6.0'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    mainClass = 'com.example.greenhouse.Main'
}

tasks.withType(GroovyCompile).configureEach {
    options.encoding = 'UTF-8'
    groovyOptions.parameters = true          // keep parameter names for reflection
}

tasks.named('test') {
    useJUnitPlatform()                        // Spock 2.x runs on the JUnit Platform
}

The same build in the Kotlin DSL (build.gradle.kts), also not run here:

plugins {
    groovy
    application
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.apache.groovy:groovy:6.0.0")
    implementation("org.apache.groovy:groovy-json:6.0.0")

    testImplementation("org.spockframework:spock-core:2.5-groovy-6.0")
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

application {
    mainClass.set("com.example.greenhouse.Main")
}

tasks.withType<GroovyCompile>().configureEach {
    options.encoding = "UTF-8"
    groovyOptions.isParameters = true
}

tasks.test {
    useJUnitPlatform()
}

Notes on both scripts:

  • The Groovy used by the build is the one declared in implementation; there is no separate Gradle setting for the Groovy version. Gradle’s own embedded Groovy, which runs Groovy DSL scripts, is a different (and older) one and is unrelated to the project’s.

  • A build that mixes several Groovy modules should not repeat the version. Import the bill of materials, described below.

  • The groovydoc task writes API documentation from the Groovy sources; it is covered in the Groovydoc page.

  • The Kotlin DSL is type safe and gives IDE completion, but each Groovy DSL idiom (groovyOptions.parameters = true) has a Kotlin spelling (isParameters) that follows Kotlin’s property naming.

The groovy-bom

The org.apache.groovy:groovy-bom artifact is a Maven bill of materials that pins every Groovy module to one version, so a dependency list names modules without versions. In Gradle it is imported as a platform; in Maven in dependencyManagement with scope import. Both snippets are not run here:

dependencies {
    implementation platform('org.apache.groovy:groovy-bom:6.0.0')
    implementation 'org.apache.groovy:groovy'
    implementation 'org.apache.groovy:groovy-json'
    implementation 'org.apache.groovy:groovy-xml'
}
dependencies {
    implementation(platform("org.apache.groovy:groovy-bom:6.0.0"))
    implementation("org.apache.groovy:groovy")
    implementation("org.apache.groovy:groovy-json")
    implementation("org.apache.groovy:groovy-xml")
}
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.groovy</groupId>
      <artifactId>groovy-bom</artifactId>
      <version>6.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Maven

Maven’s own compiler plugin knows only Java. Two plugins add Groovy:

  • GMavenPlus (org.codehaus.gmavenplus:gmavenplus-plugin) runs the standard Groovy compiler as ordinary Maven goals. It is the choice most projects make, and the one the Groovy documentation describes.

  • The Groovy-Eclipse compiler (groovy-eclipse-compiler, used as a compilerId of the Maven compiler plugin) compiles Groovy and Java together through the Eclipse compiler, so no stub generation is needed. It needs a matching groovy-eclipse-batch artifact and lags the Groovy releases, so check that it supports Groovy 6 before adopting it.

A GMavenPlus configuration, not run here (check the plugin’s current release before copying the version):

<dependencies>
  <dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>6.0.0</version>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.gmavenplus</groupId>
      <artifactId>gmavenplus-plugin</artifactId>
      <version>5.1.0</version>
      <executions>
        <execution>
          <goals>
            <goal>addSources</goal>
            <goal>addTestSources</goal>
            <goal>generateStubs</goal>
            <goal>compile</goal>
            <goal>generateTestStubs</goal>
            <goal>compileTests</goal>
            <goal>removeStubs</goal>
            <goal>removeTestStubs</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

With this configuration the Groovy and Java sources can live together in src/main/groovy (or in src/main/java), the stub goals make the joint compilation work, and mvn package produces a jar containing both. A project that has only Groovy code can drop the stub goals. GMavenPlus also has goals to run Groovy scripts during the build (execute), which is how the house pom.xml templates generate build information, and to generate Groovydoc (groovydoc).

Ant

Groovy ships two Ant tasks in the groovy-ant module: <groovyc> (compile) and <groovy> (run a script or inline code). They are declared with taskdef, giving the Groovy and Ant jars on the classpath. The following build file was run with the Ant jars from the distribution’s lib directory (java -cp ant-1.10.18.jar:ant-launcher-1.10.18.jar org.apache.tools.ant.Main -Dgroovy.home=…​), after creating src/Hello.groovy with a class Hello that has a static greet(String) method:

<project name="demo" default="all" basedir=".">
  <property name="groovy.lib" value="${groovy.home}/lib"/>
  <path id="groovy.path">
    <fileset dir="${groovy.lib}"
             includes="groovy-6.0.0.jar,groovy-ant-6.0.0.jar,groovy-templates-6.0.0.jar,groovy-xml-6.0.0.jar"/>
    <fileset dir="${groovy.lib}" includes="ant-*.jar,antlr4-runtime*.jar,*asm*.jar,picocli*.jar"/>
  </path>
  <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc" classpathref="groovy.path"/>
  <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy" classpathref="groovy.path"/>

  <target name="compile">
    <mkdir dir="classes"/>
    <groovyc srcdir="src" destdir="classes" classpathref="groovy.path"/>
  </target>

  <target name="run" depends="compile">
    <groovy classpath="classes">
      println Hello.greet(properties['greeting'] ?: 'Ant')
      println project.name
    </groovy>
  </target>

  <target name="all" depends="run"/>
</project>
compile:
  [groovyc] Compiling 1 source file to .../classes

run:
   [groovy] Hello, Ant
   [groovy] demo

all:

BUILD SUCCESSFUL

Inside the <groovy> task the script sees the Ant project, its properties, the current target and task, and can call Ant tasks through the built-in ant builder. For joint compilation, nest a <javac> element in <groovyc>; the task then generates stubs, runs javac and finishes the Groovy compilation. A real Ant installation was not available; the run above used only the bundled jars, which is the same Ant code with a more manual startup.

AntBuilder

groovy.ant.AntBuilder (in groovy-ant) is a builder that turns Ant tasks into Groovy method calls: each task is a method, its attributes are named parameters and nested elements are nested closures. It needs no build file, so it is a convenient way to use copy, zip, javac or exec from a script. The tasks print their usual Ant log lines. It is not imported by default.

import groovy.ant.AntBuilder

def ant = new AntBuilder()
ant.mkdir(dir: 'out')
ant.echo(message: 'hello from AntBuilder')
ant.copy(todir: 'out') {
    fileset(dir: 'src', includes: '**/*.txt')
}
ant.property(name: 'greeting', value: 'hi')
println ant.project.getProperty('greeting')
ant.zip(destfile: 'out/src.zip', basedir: 'src')
println new File('out/src.zip').exists()
println new File('out').list().sort()
try {
    ant.fail(message: 'boom')
} catch (org.apache.tools.ant.BuildException e) {
    println "caught: ${e.message}"
}
     [echo] hello from AntBuilder
     [copy] Copying 1 file to .../out
hi
      [zip] Building zip: .../out/src.zip
true
[a.txt, src.zip]
caught: boom

Ant failures surface as BuildException, which a script can catch. ant.project gives the underlying Ant Project, whose properties are the way to pass values between tasks. Besides scripts, Gradle builds have used ant (the same builder) to reuse Ant tasks.

Gant (legacy)

Gant was a Groovy front end to Ant: build files were Groovy scripts with targets as closures and Ant tasks as method calls. It is a legacy tool: it was written for Groovy 1.x and 2.x, and Gradle and the builders above replaced it, so it should not be adopted for new work and is not shown running here. When an old Gant build must be migrated, the usual target is to turn its targets into Gradle tasks or into plain AntBuilder scripts.

Pitfalls

  • Groovy 4.0 moved the artifacts from org.codehaus.groovy to org.apache.groovy. A build that still has the old group resolves an old Groovy, or fails to find the new modules.

  • The Groovy version in the build must be a version that can run on the JDK used. Groovy 6.0 requires JDK 17 or newer.

  • Spock publishes a build per Groovy line, such as 2.5-groovy-6.0; a mismatched Spock artifact fails at test start-up.

  • Optional modules (groovy-json, groovy-xml, groovy-sql, groovy-templates and others) are separate jars; having the main groovy jar alone is not enough for the classes they provide.

  • AntBuilder and the <groovy> task run in the build’s JVM and classloader; a long-lived build daemon can keep classes loaded between runs.

  • Do not put credentials in a build file. Pass repository credentials from the environment or the build tool’s secret store.

Where the book is outdated

Books from the 1.x and 2.x era show Ant and Gant for build automation and describe AntBuilder in the default packages. In current Groovy AntBuilder is in groovy.ant and needs an import, Gant is no longer maintained for modern Groovy, and Gradle (and Maven with GMavenPlus) are the usual tools. They also use the old org.codehaus.groovy group, which was replaced by org.apache.groovy in 4.0.