In this article i will show you how to create a gradle based multiple module project. We will create different modules and we will see how we can utilize the common dependencies across different modules and also we will see how to add the dependency of one module within the other module.

Create a gradle based project

You can create a new project from IntelliJ Idea and here from the New Project:

After that you can see the same setup as i have right now:

Explorer the gradle-based project

In the usual gradle-based projects, you will find all of these files such as: build.gradle, settings.gradle, and the gradle folder that consists of the gradle wrapper. You can find the executables for Windows (gradlew.bat) or Linux based systems (gradlew). The beauty of the gradle wrapper is you do not need to install anything on your system. With the help of these executables, you can run all the gradle-based commands.

We have the build.gradle file, it will show all the tasks available in our build.

$ ./gradlew tasks

Welcome to Gradle 8.0!

For more details see https://docs.gradle.org/8.0/release-notes.html


> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'multi-module-setup'
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'multi-module-setup'.
dependencies - Displays all dependencies declared in root project 'multi-module-setup'.
dependencyInsight - Displays the insight into a specific dependency in root project 'multi-module-setup'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of root project 'multi-module-setup'.
projects - Displays the sub-projects of root project 'multi-module-setup'.
properties - Displays the properties of root project 'multi-module-setup'.
resolvableConfigurations - Displays the configurations that can be resolved in root project 'multi-module-setup'.
tasks - Displays the tasks runnable from root project 'multi-module-setup'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the test suite.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.

To see all tasks and more detail, run gradlew tasks --all

To see more detail about a task, run gradlew help --task <task>

BUILD SUCCESSFUL in 773ms
1 actionable task: 1 executed

So now you can see we have some of the basic tasks, these are the default gradle tasks.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'multi-module-setup'.
dependencies - Displays all dependencies declared in root project 'multi-module-setup'.
dependencyInsight - Displays the insight into a specific dependency in root project 'multi-module-setup'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of root project 'multi-module-setup'.
projects - Displays the sub-projects of root project 'multi-module-setup'.
properties - Displays the properties of root project 'multi-module-setup'.
resolvableConfigurations - Displays the configurations that can be resolved in root project 'multi-module-setup'.
tasks - Displays the tasks runnable from root project 'multi-module-setup'.

And apart from that we have some of the tasks which are coming from Java plugin

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

As we have added Java plugin in our build.gradle code

plugins {
    id 'java'
}

...

If we remove all of the configuration in build.gradle , we see only the gradle-based tasks, but now you cannot see any tasks related to java. Then, whatever plugins we will add in our build.gradle file, tasks related to those plugins will be available in our build.

For the java, we can run the build task, this will trigger a lot of action, this is the default task of java. This bellow command with be ran with --info option, it will show all of the information of this task.

$ ./gradlew build --info

...
Starting Build
Settings evaluated using settings file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/settings.gradle'.
Projects loaded. Root project using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
Included projects: [root project 'multi-module-setup']
...

Let’s look from the top we were running the build task so here it starts first of all it will evaluate this settings.gradle file. In this file, whatever modules are there it will pick all of those modules. Right now, we have just the parent module which is multi-module-setup , so it will evaluate this module, and in the configuration phase, it will check the build.gradle file of that particular module.

...
> Configure project :
Evaluating root project 'multi-module-setup' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
All projects evaluated.
Task name matched 'build'
Selected primary task 'build' from project :
The configuration :mainSourceElements is both consumable and declarable. This combination is incorrect, only one of these flags should be set.
The configuration :testResultsElementsForTest is both consumable and declarable. This combination is incorrect, only one of these flags should be set.
Tasks to be executed: [task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':assemble', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test', task ':check', task ':build']
Tasks that were excluded: []
Resolve mutations for :compileJava (Thread[Execution worker,5,main]) started.
:compileJava (Thread[Execution worker,5,main]) started.
...

Here in the root module, we have this build.gradle file which has plugin java, it will pick all the tasks of java plugin which are related to build and it has prepared the list of tasks that has to run in the order of the task.

Here the build has find out these tasks to be executed: :compileJava, :processResources, :classes, :jar, :assemble, :compileTestJava, :processTestResources, :processResources, :test, :check, :build

So one by one, all of these tasks will be running and in each task the particular action will be done like:

  • :compileJava -> java classes will be compiled
  • :processResources -> it will process the resource folder
  • :classes -> it will create the classes
  • :jar -> it will prepare the jar file,

Then it has created a build folder , this build folder consists of the artifacts that has been created after the task that we were running.

Add new modules into project

Now because we have only one module so here everything is running for that particular module which is the parent module. Let’s see how we can add a new module in our project, for that we can go to the multi-module-setup project, right click -> New -> Module :

Here we will create a module-1 and it will be added within the parent multi-module-setup module.

Within the module-1, there is the src folder and then we have the build.gradle file. The difference in the module and the root project is in the module we will only find the build.gradle file but in the root module we have all the gradle wrapper, gradle executables and the settings.gradle file. That means whenever we are running a gradle task it will be running on the top of root project. We can run the module specific tasks as well, we can see that in the build.gradle file of the module-1, we have same thing as we have in the parent module.

Let’s run the build for now, in the evaluation phase:

$ ./gradlew build --info

...
Starting Build
Settings evaluated using settings file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/settings.gradle'.
Projects loaded. Root project using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
Included projects: [root project 'multi-module-setup', project ':module-1']
...

We have two modules, the root project is multi-module-setup and the sub project is :module-1. And if we look into the settings.gradle file:

rootProject.name = 'multi-module-setup'
include 'module-1'

Here we have got the module-1 automatically included for us. The configruation is happening for each of these module, and all the tasks of the module-1 also be evaluated now.

If we need to run only task of the child module, then we can do that by applying the colon and the module name then the task we want to run:

$ ./gradlew :module-1:build --info

...
Tasks to be executed: [task ':module-1:compileJava', task ':module-1:processResources', task ':module-1:classes', task ':module-1:jar', task ':module-1:assemble', task ':module-1:compileTestJava', task ':module-1:processTestResources', task ':module-1:testClasses', task ':module-1:test', task ':module-1:check', task ':module-1:build']
...

Here it is running the tasks for the module-1 only.

Let’s look at the module-1 the build.gradle file has the same task as we have in the parent module. It means that we have a duplicate files, we should have the configuration in only one module. Let’s remove the configuration for the module-1 and now we should expect that the parent module build.gradle file should run the task for both the child and the parent. To do that, we need to add something in the parent build.gradle file.

The first option is using allprojects tag:

plugins {
    id 'java'
}

allprojects {

    group = 'org.favtuts'
    version = '1.0-SNAPSHOT'

    repositories {
        mavenCentral()
    }

    dependencies {

    }

    test {
        useJUnitPlatform()
    }

}

Then we can try to build:

$ ./gradlew build --info

...
Tasks to be executed: [task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':assemble', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test', task ':check', task ':build']
...

So you can see it did not run any tasks for the module-1 . To fix the issue, we need to apply the java plugin for in the new configuration

allprojects {

    apply plugin:'java'
     
    ...
}

When we apply the plugin, all of the tasks of that plugin will be applied to the module that we are referring here. We are referring: allprojects. It means that this plugin we be applied in all of the modules of that project. Now we can check build again:

$ ./gradlew build --info

...
Tasks to be executed: [task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':assemble', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test', task ':check', task ':build', task ':module-1:compileJava', task ':module-1:processResources', task ':module-1:classes', task ':module-1:jar', task ':module-1:assemble', task ':module-1:compileTestJava', task ':module-1:processTestResources', task ':module-1:testClasses', task ':module-1:test', task ':module-1:check', task ':module-1:build']
...

Here you can see we have all the tasks running for the module-1 as well.

Now in the build.gradle file of the module-1, if we add few specific plugins then those plugins will only be part of the module-1

plugins {
    id 'groovy'
}

Let’s build again

$ ./gradlew build --info

...
Tasks to be executed: [task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':assemble', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test', task ':check', task ':build', task ':module-1:compileJava', task ':module-1:compileGroovy', task ':module-1:processResources', task ':module-1:classes', task ':module-1:jar', task ':module-1:assemble', task ':module-1:compileTestJava', task ':module-1:compileTestGroovy', task ':module-1:processTestResources', task ':module-1:testClasses', task ':module-1:test', task ':module-1:check', task ':module-1:build']
...

You can see we have the tasks for groovy on the module-1 only.

You can check the evaluation for which project by adding the log printing:

allprojects {

    ...
    println "Hello ${project.name}"
    ...
}

Then when you run the build again you can see the Hello messages:

$ ./gradlew build --info

...
> Configure project :
Evaluating root project 'multi-module-setup' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
Compiling build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle' using SubsetScriptTransformer.
Compiling build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle' using BuildScriptTransformer.
Hello multi-module-setup
Hello module-1
...

The second option is using subprojects tag:

subprojects {

    println "Hello ${project.name}"

}

Then when you run the build again it only prints the module-1 project

$ ./gradlew build --info

...
> Configure project :
Evaluating root project 'multi-module-setup' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
Hello module-1
...

Now let’s add the new module-2 and check the build again

$ ./gradlew build --info

...
> Configure project :
Evaluating root project 'multi-module-setup' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/build.gradle'.
Hello module-1
Hello module-2

> Configure project :module-1
Evaluating project ':module-1' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/module-1/build.gradle'.

> Configure project :module-2
Evaluating project ':module-2' using build file '/home/tvt/tuts.heomi.net/java/java-gradle-tutorials/multi-module-setup/module-2/build.gradle'.
...

If we want to run some of the configuration for the specific modules and not for all the modules, we need to add the condition to check:

subprojects {

    println "Hello ${project.name}"
    if (['module-1'].contains(project.name)) {
        apply plugin: 'java'

        group = 'org.favtuts'
        version = '1.0-SNAPSHOT'

        repositories {
            mavenCentral()
        }

        dependencies {

        }

        test {
            useJUnitPlatform()
        }
    }
}

It’s better if we using configure closure:

plugins {
    id 'java'
}

def javaProjects = [
        project(':module-1')
]

configure(javaProjects) {
    println "Hello ${project.name}"
    if (['module-1'].contains(project.name)) {
        apply plugin: 'java'

        group = 'org.favtuts'
        version = '1.0-SNAPSHOT'

        repositories {
            mavenCentral()
        }

        dependencies {

        }

        test {
            useJUnitPlatform()
        }
    }
}

subprojects {

}

Include one module within the other

In module-1, we have the DateUtil class which has getDate method:

package org.favtuts;

import java.util.Date;

public class DateUtil {
    public static Date getCurrentDate() {
        return new Date();
    }
}

In module-2, to use the DateUtil class from the module-1, we need to add dependencies in the build.gradle of the module-2:

dependencies {
    implementation project(':module-1')
}

Then we need to build project then this DateUtil class will be available to use in the module-2 at the Application class

package org.favtuts;

public class Application {
    public static void main(String[] args) {
        System.out.printf("Hello from App %s current time : %s", Application.class, DateUtil.getCurrentDate());
    }
}

Then we can run this application:

Then you can see the result:

Basically this is how you can add the dependency of one module into the other module.

You can checkout Github repository: https://github.com/favtuts/java-gradle-tutorials/tree/main/multi-module-setup

Leave a Reply

Your email address will not be published. Required fields are marked *