Creating a Gradle multi-module project

Using Gradle multi-module build is easy and makes Spring Boot application development just better. Refactor your code to a multi-module structure, and improve quality and maintainability with this tutorial.

Creating a Gradle multi-module project

A modern Java Spring project often has multiple parts that would make sense to separate, be able to build, and deploy separately. These can often be the core of a service, and a client for other services to use when they want to call ours. Another scenario is when we are creating multiple implementations for some interface, let's say we integrate multiple third parties (accounting software for example). We want our core service and logic to be third-party independent so we define interfaces between the partner-dependent implementations and the core logic. The core service, the interface package, and all the implementations can be separate modules.

This structuring makes our code nicely structured and easier to manage the modules independently. By defining the dependencies between our modules, we can enforce rules on what can be called avoiding creating spaghetti code and keeping everything nice and tidy. So how can we create these awesome modules?

Multi-module project

Project structure

Both Gradle and Maven support multi-module builds, in this article I will show you the Gradle version as I'm more familiar with that.

In my example, we will create a core service, a plugin interface, and 2 implementations of the interface that the core service uses.

Dependencies between the modules
  • The service defines the core business logic, our program that we run
  • The interface module has all the interfaces and model objects that the service can call to use the concrete implementations
  • Implementations A and B are 2 implementations of the logic needed by the service

The project structure for this looks like the one below.

The root project is called multi-modules, that one does not have a build.gradle file, only a settings.gradle with the following content, defining the submodules.

rootProject.name = 'multi-modules'

include 'interface'
include 'service'
include 'implementation-A'
include 'implementation-B'

Dependencies between the modules can be defined in the build.gradle files, in the dependencies section like this (from the service module):

dependencies {
    implementation project(":interface")
    implementation project(":implementation-A")
    implementation project(":implementation-B")
}
⚠️
Gradle does not allow circular dependencies between modules! If you would need one, that probably means suboptimal design.

Sharing configurations

Each submodule has its own build.gradle file with all the configurations and settings for the given module. This can however lead to many duplications amongst the files. We want to use the Gradle Java and other plugins in each module, we may also want to use the same external dependency in multiple modules. Adding this separately to each individual build.gradle would be very hard to maintain and messy. Lucky for us we can create local Gradle plugins called convention plugins in the special buildSrc folder that define these common dependencies, configurations, and build scripts. Afterward, we just have to add these custom plugins to all our modules where needed.

In our example the structure of the buildSrc folder looks like this:

build.gradle:

plugins {
    id 'groovy-gradle-plugin'
}

multimodule-config.gradle:

plugins {
    id 'java'
}

sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

test {
    useJUnitPlatform()
}

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.24'
    annotationProcessor 'org.projectlombok:lombok:1.18.24'

    implementation 'ch.qos.logback:logback-classic:1.2.11'
}

This convention plugin called multimodule-config when applied will add the Java plugin, source, and target versions repositories test platform to use and a few dependencies to each subproject. I used this for all, for example in the service module I just add the lines

plugins {
    id 'multimodule-config'
}

to my build.gradle and that is it. We can create a lot of these convention plugins to group specific dependencies and configurations like spring-conventions, java-conventions and so on.

There are more tricky situations, unfortunately. Let's say we have a Spring project and multiple modules require Spring boot dependencies. We want to use and specify the Spring Gradle plugin and all spring dependencies only once. If we try to add the Spring plugin to our convention plugin we will get an error.

multimodule-gradle.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.0'
}

...

Will produce the following issue:

Invalid plugin request [id: 'org.springframework.boot', version: '2.7.0']. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'org.springframework.boot' is an implementation dependency

If we simply omit the version number the build won't find the plugin and fail. What we have to do is to add the Spring boot plugin as an implementation dependency in the build.gradle file for the buildSrc folder. Don't forget to add the maven central repository too!

plugins {
    id 'groovy-gradle-plugin'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-gradle-plugin:2.7.0'
}

And this is how you create a Spring Boot Gradle multi-module project!

Examples from this article can be found here:

tmsvr-blog/multi-modules at master · Timester/tmsvr-blog
Materials featured on my blog tmsvr.com. Contribute to Timester/tmsvr-blog development by creating an account on GitHub.

Further info on Gradle multi-module usage:

Structuring and Building a Software Component with Gradle