Setting up Gradle for a multi-module Groovy project is one of those tasks that can save you hours of build time and headaches or create a tangled mess of dependencies if done wrong. If you've ever worked on a growing Groovy codebase, you know that keeping everything in a single module becomes unmanageable fast. Splitting your project into modules makes code easier to maintain, test, and deploy independently. The key is getting your Gradle build configuration right from the start so modules communicate cleanly and builds stay fast.

What does a multi-module Groovy project actually look like?

A multi-module Groovy project is a single Gradle project that contains multiple subprojects (modules). Each module has its own build.gradle file, source directories, and test directories. A root settings.gradle file ties them all together. This structure is common in applications where you want to separate concerns for example, a core library module, a web layer module, and a shared utilities module, all written in Groovy.

The directory structure typically looks like this:

  • Root project contains settings.gradle, the root build.gradle, and shared configuration
  • Module A e.g., a core library with its own build.gradle and src/main/groovy
  • Module B e.g., a web service that depends on Module A
  • Module C e.g., shared test utilities used across modules

How do you set up settings.gradle for multiple Groovy modules?

The settings.gradle file in your project root is where you register all subprojects. This file tells Gradle which modules belong to your build.

Here is a basic example with three modules:

settings.gradle

rootProject.name = 'my-groovy-app'

include 'core'
include 'web'
include 'shared-utils'

Each entry after include should match the directory name of the subproject. If your modules live inside a modules/ folder, you'd write include 'modules:core' instead. Gradle uses the colon as a path separator for nested modules.

How should you configure the root build.gradle?

The root build.gradle is where you apply plugins and set defaults for all subprojects. Using the subprojects block avoids repeating the same configuration in every module.

subprojects {
 apply plugin: 'groovy'

 sourceCompatibility = '11'
 targetCompatibility = '11'

 repositories {
 mavenCentral()
 }

 dependencies {
 implementation 'org.codehaus.groovy:groovy-all:4.0.15'
 testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
 }
}

This setup applies the Groovy plugin to every module, sets Java compatibility, and ensures all subprojects use the same Groovy version and testing framework. You can also use the allprojects block if you need configuration that includes the root project itself.

If you want to go further with code quality enforcement across all modules, our article on top Groovy code quality and static analysis plugins covers tools you can apply at the root level to keep every module consistent.

How do modules depend on each other?

When one module needs code from another, you declare an project dependency in that module's build.gradle. This is one of the main advantages of multi-module builds Gradle understands the dependency graph and builds modules in the right order.

For example, if the web module depends on the core module:

web/build.gradle

dependencies {
 implementation project(':core')
 implementation project(':shared-utils')
}

Gradle will build core and shared-utils before building web. If there's a circular dependency between modules, Gradle will fail the build and tell you exactly which modules are involved.

Should you use a convention plugin or a shared script?

As your project grows, the subprojects block in the root build.gradle can become unwieldy. Two cleaner alternatives exist:

BuildSrc convention plugins: Create a buildSrc/ directory at the root and write Groovy-based plugin classes that define conventions. Each module then applies the convention plugin instead of repeating boilerplate.

// buildSrc/src/main/groovy/myorg.groovy-conventions.gradle
apply plugin: 'groovy'

repositories {
 mavenCentral()
}

dependencies {
 implementation 'org.codehaus.groovy:groovy-all:4.0.15'
}

Then in each module's build.gradle:

plugins {
 id 'myorg.groovy-conventions'
}

Apply from a shared script: You can also use apply from: "$rootDir/gradle/common.gradle" to pull in shared configuration. This approach is simpler but less type-safe than buildSrc plugins.

If you're evaluating which Gradle plugins pair best with Groovy modules, take a look at our breakdown of the best Gradle plugins for Groovy development.

How do you configure different Groovy compilation options per module?

Not every module needs the same compiler settings. A utility module might compile with different options than a web-facing module. You can override root-level defaults in each module's own build.gradle.

The GroovyCompile task lets you set compiler flags like AST transformations or script base classes:

tasks.withType(GroovyCompile).configureEach {
 groovyOptions.configurationScript = file("$rootDir/gradle/groovy-compiler-config.groovy")
 options.encoding = 'UTF-8'
}

This is useful for enabling specific Groovy compiler features like static compilation (@CompileStatic) at the module level.

What are common mistakes when configuring Gradle for multi-module Groovy projects?

  • Repeating configuration in every module Use the subprojects block or convention plugins to centralize shared settings. Duplicated config drifts over time.
  • Not using consistent Groovy versions If one module uses Groovy 4.0 and another uses 3.0, you'll get runtime errors. Enforce a version in the root build.
  • Forgetting to declare inter-module dependencies Just because modules live in the same project doesn't mean they can see each other's classes. You must declare project(':moduleName') dependencies explicitly.
  • Ignoring the buildSrc folder Many teams overlook buildSrc and end up with messy root build files. BuildSrc gives you type-safe, reusable build logic written in Groovy itself.
  • Mixing Groovy and Java compilation order If modules mix Groovy and Java sources, the groovy-eclipse compiler or joint compilation setup matters. Misconfiguration leads to missing class errors.
  • Not enabling build caching for large projects Multi-module builds can get slow without caching. Enable the org.gradle.caching=true property in your gradle.properties file.

How do you run and test individual modules?

One of the practical benefits of a multi-module layout is targeted builds. You don't have to build everything every time.

  • Build a single module: gradle :core:build
  • Run tests for one module: gradle :web:test
  • Build everything: gradle build
  • See the dependency graph: gradle :web:dependencies

The dependency report is especially helpful for debugging version conflicts between modules.

How does Gradle performance compare in multi-module builds?

Multi-module builds can be slower if configured poorly, but Gradle's parallel execution and incremental compilation make a big difference. Enable parallel builds in gradle.properties:

org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true

If you're curious how Gradle stacks up against other build tools for Groovy projects, we covered Maven versus Gradle performance benchmarks with real numbers from Groovy application builds.

How do you handle shared dependencies and version catalogs?

Gradle's version catalogs (available since Gradle 7.0) let you define dependency versions in one place. Create a gradle/libs.versions.toml file:

[versions]
groovy = "4.0.15"
spock = "2.3-groovy-4.0"

[libraries]
groovy-all = { module = "org.codehaus.groovy:groovy-all", version.ref = "groovy" }
spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" }

Then reference these in any module's build.gradle:

dependencies {
 implementation libs.groovy.all
 testImplementation libs.spock.core
}

This approach keeps versions in sync across every module without scattering version strings throughout your build files.

Practical checklist for configuring Gradle multi-module Groovy builds

  1. Create a settings.gradle in the root and register all modules with include
  2. Set up a root build.gradle with a subprojects block for shared Groovy plugin config and dependencies
  3. Declare inter-module dependencies using project(':moduleName')
  4. Use buildSrc convention plugins to avoid duplication as your module count grows
  5. Enforce consistent Groovy and dependency versions through version catalogs
  6. Enable parallel builds, caching, and the Gradle daemon in gradle.properties
  7. Test individual modules with gradle :moduleName:test to verify isolation
  8. Run gradle :moduleName:dependencies regularly to catch version conflicts early

Start with a simple two-module structure, get the build working end to end, and add convention plugins as your project grows. A well-structured multi-module Groovy build with Gradle pays off quickly when your team and codebase expand. If you want to add a creative touch to your project documentation or README files, consider using a clean typeface like Poppins for a modern, readable look.

Get Started