Getting Started With Gradle: Creating a Multi-Project Build

Although we can create a working application by using only one module, sometimes it is wiser to divide our application into multiple smaller modules.

Because this is a rather common use case, every self-respecting build tool must support it, and Gradle is no exception. If a Gradle project has more than one module, it is called a multi-project build.

This blog post describes how we can create a multi-project build with Gradle.

Let’s start by taking a look at the requirements of our Gradle build.

Additional Reading:

If you are not familiar with Gradle, you should read the following blog posts before you continue reading this blog post:

The Requirements of Our Gradle Build

Our example application has two modules:

  • The core module contains the common components that are used by the other modules of our application. In our case, it contains only one class: the MessageService class returns the string 'Hello World!'. This module has only one dependency: it has one unit test that uses Junit 4.11.
  • The app module contains the HelloWorld class that starts our application, gets a message from a MessageService object, and writes the received message to a log file. This module has two dependencies: it needs the core module and uses Log4j 1.2.17 as a logging library.

Our Gradle build has also two other requirements:

  • We must be able to run our application with Gradle.
  • We must be able to create a runnable binary distribution that doesn’t use the so called “fat jar” approach.
If you don’t know how you can run your application and create a runnable binary distribution with Gradle, you should read the following blog post before you continue reading this blog post:

Let’s move on and find out how we can create a multi-project build that fulfills our requirements.

Creating a Multi-Project Build

Our next step is to create a multi-project Gradle build that has two subprojects: app and core. Let’s start by creating the directory structure of our Gradle build.

Creating the Directory Structure

Because the core and app modules use Java, they both use the default project layout of a Java project. We can create the correct directory structure by following these steps:

  1. Create the root directory of the core module (core) and create the following the subdirectories:
    • The src/main/java directory contains the source code of the core module.
    • The src/test/java directory contains the unit tests of the core module.
  2. Create the root directory of the app module (app) and create the following subdirectories:
    • The src/main/java directory contains the source code of the app module.
    • The src/main/resources directory contains the resources of the app module.

We have now created the required directories. Our next step is to configure our Gradle build. Let’s start by configuring the projects that are included in our multi-project build.

Configuring the Projects that Are Included in Our Multi-Project Build

We can configure the projects that are included in our multi-project build by following these steps:

  1. Create the settings.gradle file to the root directory of the root project. A multi-project Gradle build must have this file because it specifies the projects that are included in the multi-project build.
  2. Ensure that the app and core projects are included in our multi-project build.

Our settings.gradle file looks as follows:

include 'app'
include 'core'

Let’s move on and configure the core project.

Configuring the Core Project

We can configure the core project by following these steps:

  1. Create the build.gradle file to the root directory of the core project.
  2. Create a Java project by applying the Java plugin.
  3. Ensure that the core project gets its dependencies from the central Maven2 repository.
  4. Declare the JUnit dependency (version 4.11) and use the testCompile configuration. This configuration describes that the core project needs the JUnit library before its unit tests can be compiled.

The build.gradle file of the core project looks as follows:

apply plugin: 'java'

repositories {
	mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.11'
}

Let’s move on and configure the app project.

Configuring the App Project

Before we can configure the app project, we have to take a quick look at the dependency management of such dependencies that are part of the same multi-project build. These dependencies are called project dependencies.

If our multi-project build has projects A and B, and the compilation of the project B requires the project A, we can configure this dependency by adding the following dependency declaration to the build.gradle file of the project B:

dependencies {
    compile project(':A')
}

We can now configure the app project by following these steps:

  1. Create the build.gradle file to the root directory of the app project.
  2. Create a Java project by applying the Java plugin.
  3. Ensure that the app project gets its dependencies from the central Maven2 repository.
  4. Configure the required dependencies. The app project has two dependencies that are required when it is compiled:
    • Log4j (version 1.2.17)
    • The core module
  5. Create a runnable binary distribution.

The build.gradle file of the app project looks as follows:

apply plugin: 'application'
apply plugin: 'java'

repositories {
	mavenCentral()
}

dependencies {
    compile 'log4j:log4j:1.2.17'
    compile project(':core')
}

mainClassName = 'net.petrikainulainen.gradle.client.HelloWorld'

task copyLicense {
    outputs.file new File("$buildDir/LICENSE")
    doLast {
        copy {
            from "LICENSE"
            into "$buildDir"
        }
    }
}

applicationDistribution.from(copyLicense) {
    into ""
}

Let’s move on and remove the duplicate configuration found from the build scripts of the core and app projects.

Removing Duplicate Configuration

When we configured the subprojects of our multi-project build, we added duplicate configuration to the build scripts of the core and app projects:

  • Because both projects are Java projects, they apply the Java plugin.
  • Both projects use the central Maven 2 repository.

In other words, both build scripts contain the following configuration:

apply plugin: 'java'

repositories {
	mavenCentral()
}

Let’s move this configuration to the build.gradle file of our root project. Before we can do this, we have to learn how we can configure our subprojects in the build.gradle file of our root project.

If we want to add configuration to a single subproject called core, we have to add the following snippet to the build.gradle file of our root project:

project(':core') {
	//Add core specific configuration here
}

In other words, if we want to move the duplicate configuration to the build script of our root project, we have to add the following configuration to its build.gradle file:

project(':app') {
	apply plugin: 'java'

	repositories {
		mavenCentral()
	}
}

project(':core') {
	apply plugin: 'java'

	repositories {
		mavenCentral()
	}
}

This doesn't really change our situation. We still have duplicate configuration in our build scripts. The only difference is that the duplicate configuration is now found from the build.gradle file of our root project. Let's eliminate this duplicate configuration.

If we want to add common configuration to the subprojects of our root project, we have to add the following snippet to the build.gradle file of our root project:

subprojects {
	//Add common configuration here
}

After we have removed the duplicate configuration from the build.gradle file of our root project, it looks as follows:

subprojects {
    apply plugin: 'java'

    repositories {
        mavenCentral()
    }
}
If we have configuration that is shared by all projects of our multi-project build, we should add the following snippet to the build.gradle file of our root project:

allprojects {
	//Add configuration here
}

Additional Reading:

We can now remove the duplicate configuration from the build scripts of our subprojects. The new build scripts of our subprojects looks as follows:

The core/build.gradle file looks as follows:

dependencies {
    testCompile 'junit:junit:4.11'
}

The app/build.gradle file looks as follows:

apply plugin: 'application'

dependencies {
    compile 'log4j:log4j:1.2.17'
    compile project(':core')
}

mainClassName = 'net.petrikainulainen.gradle.client.HelloWorld'

task copyLicense {
    outputs.file new File("$buildDir/LICENSE")
    doLast {
        copy {
            from "LICENSE"
            into "$buildDir"
        }
    }
}

applicationDistribution.from(copyLicense) {
    into ""
}

We have now created a multi-project Gradle build. Let's find out what we just did.

What Did We Just Do?

When we run the command gradle projects in the root directory of our multi-project build, we see the following output:

> gradle projects
:projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'multi-project-build'
+--- Project ':app'
\--- Project ':core'

To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :app:tasks

BUILD SUCCESSFUL

As we can see, this command lists the subprojects (app and core) of our root project. This means that we have just created a multi-project Gradle build that has two subprojects.

When we run the command gradle tasks in the root directory of our multi-project build, we see the following output (only relevant part of it is shown below):

> gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Application tasks
-----------------
distTar - Bundles the project as a JVM application with libs and OS specific scripts.
distZip - Bundles the project as a JVM application with libs and OS specific scripts.
installApp -Installs the project as a JVM application along with libs and OS specific scripts
run - Runs this project as a JVM application 

As we can see, we can run our application by using Gradle and create a binary distribution that doesn’t use the so called “fat jar” approach. This means that we have fulfilled all requirements of our Gradle build.

Let’s move on and find out what we learned from this blog post.

Summary

This blog post has taught us three things:

  • A multi-project build must have the settings.gradle file in the root directory of the root project because it specifies the projects that are included in the multi-project build.
  • If we have to add common configuration or behavior to all projects of our multi-project build, we should add this configuration (use allprojects) to the build.gradle file of our root project.
  • If we have to add common configuration or behavior to the subprojects of our root project, we should add this configuration (use subprojects) to the build.gradle file of our root project.

P.S. You can get the example application of this blog post from Github.

If you want to learn how to use Gradle, you should take a look at my Gradle tutorial.
40 comments… add one
  • Chris May 1, 2015 @ 16:45

    This was very helpful - I was very close to having this work on my own, but not quite. After reading and trying your example, I was able to get it working (I put the distribution task in the main gradle, rather than the app sub-project gradle). Thanks for this article!

    • Petri May 1, 2015 @ 17:13

      You are welcome! It is nice to hear that this blog post helped you to solve your problem.

      • Shyam Feb 15, 2016 @ 11:46

        Hi Petri,
        You blogs and information in these blogs are incredible. Great job , thank you so much.
        Look forward to more such informative and descriptive blogs in future too.

        • Petri Feb 16, 2016 @ 21:28

          Hi Shyam,

          Thank you for your kind words. I really appreciate them.

  • Jigish Jun 2, 2015 @ 21:57

    Petri,
    I have been pulling my hair since so long to get ivy multiple projects working together but found this as a consequence, I am so glad that i landed to your blog. This will be very helpful to me.

    • Petri Jun 2, 2015 @ 22:59

      Hi,

      Thank you for your kind words. I really appreciate them. Also, it is nice to know that this blog post will be useful to you. :)

  • Adnan A M Jun 22, 2015 @ 10:27

    Could you share your views on how to maintain different build flavors across different modules ?

  • Niran Jul 3, 2015 @ 8:12

    On using gradle init, I get java.StackOverflow error.

    PLease let me know the workaround/reason for this??

    • Petri Jul 3, 2015 @ 14:39

      Which Gradle (and Java) version are you using?

  • smoker Oct 9, 2015 @ 8:43

    Cool, thanks

    • Petri Oct 9, 2015 @ 16:28

      You are welcome!

  • Igor Dec 2, 2015 @ 5:03

    Hello!
    I'm bumping into an issue, maybe you can help me. Any help will be appreciated.
    So, I got a gradle multiproject spring boot, and one of the projects is the domain, repositories, etc, which is a spring-boot-data-jpa project.
    Some other projects, that may be deployed independently, depend on this domain project.
    Well, so far I couldn't make this scenario work. Either I need to spring-boot the domain project, which doesn't make much sense to me, and then the project compiles but doesn't bootRun, or I remove sprint-boot plugin and then project doesn't compile.

    I've created a gist...
    https://gist.github.com/igordonin/bd1180e161ecf7681b2c

    Again, thanks for any advice.

    • Petri Dec 2, 2015 @ 17:51

      I have to admit that I don't any "real life" experience from using Gradle in a multi-module project, but I assume that you have to declare the required the dependencies in the domain project (or it doesn't compile). The problem is that I have no idea what you should do if you want to use a Spring Boot starter POM. Have you tried following the advice given in this blog post?

  • SGB Dec 28, 2015 @ 0:48

    ++
    I am a maven user and I have always used Eclipse to create my multi-module maven projects. But I was having a tough time figuring out how to do the same for a gradle app. This blog post helped me finally understand how to create a multi-module gradle project. Thank you!

    sgb

    • Petri Dec 28, 2015 @ 10:20

      You are welcome. I am happy to hear that this blog post was useful to you.

  • Tanya Feb 16, 2016 @ 2:52

    Hi Petri,
    Great blog post. I had a question on gradle. I have a muti-project with 10 sub projects and I noticed that it runs sequentially and if let's say the third project has a failed unit test, it shows the error for the third sub project, exits and the rest of the build does not run. Have you seen this and is there a way around this?

    • Petri Feb 16, 2016 @ 21:37

      Hi Tanya,

      Check out this discussion. It provides several solutions to your problem (pick the one that you like the most).

      • Tanya Feb 17, 2016 @ 5:18

        Petri,
        This answers my question.
        Thank you. Keep up the good work on the blog :)
        Tanya

        • Petri Feb 18, 2016 @ 18:28

          Hi Tanya,

          You are welcome. I will continue writing new posts on a regular basis!

  • Deepak Bisht Mar 24, 2017 @ 7:47

    Thanks a lot.. It really helped a lot.. Nice illustration. Thanks again.

    • Petri Mar 27, 2017 @ 21:03

      You are welcome. Also, thank you for your kind words. I really appreciate them.

  • Venkatesh Apr 8, 2017 @ 12:51

    Hi
    Can we build the same project source with two java versions and different dependencies?
    I have a project compiled with the 1.5 and now I'm upgrading to 1.8 .but still some application needs 1.5.
    Could you please help me in this .

  • houcem May 19, 2017 @ 16:07

    Hi,
    I've been trying to convert a mutli module project from maven to gradle, but each module has its own pom.xml & I don't have a pom.xml in the root of the project, how can I convert it ??

    thanks

  • Amarnath Oct 8, 2017 @ 23:18

    Hi Petri,
    Great tutorial. Thanks a lot.
    One question, we have two modules 1. messageservice 2. appservice.
    appservice has the main class.
    --------------------------------

    
    package com.example;
    // import com.example.MessageService;
    public class App {
      public static void main(String[] args) {
        MessageService messageservice = new MessageService();
        System.out.println("App prints: " + messageservice.getMessage());
      } }
    
    

    --------------------------------
    Here even though I have not imported MessageService class it is still running. I am not sure why because MessageService class is in another module so appservice need to import the class right?
    I even extracted the appservice.jar to see whether it had MessageService class or not but it didn't. Could you please explain this.

    • Amarnath Oct 9, 2017 @ 9:38

      Got it. I guess, as both the classes are in the same package we don't need to import.

      • Petri Oct 10, 2017 @ 21:49

        Hi,

        You are right. If both classes are in the same package, you don't need to import them.

  • Binh Thanh Nguyen Nov 20, 2017 @ 2:48

    Thanks, nice post!

    • Petri Nov 22, 2017 @ 21:58

      You are welcome!

  • sri Mar 5, 2019 @ 20:33

    Hi,
    Thanks for the article. Do you know how to run root configuration after all sub modules are configured?

  • Phani Sep 18, 2019 @ 11:39

    I want to access the only one from from the implemented project in gradle android.how can i access the only one file and rest of the files are not required

    • Petri Sep 19, 2019 @ 21:58

      Hi,

      Unfortunately, I have no experience from Android development. I think that you should take look at the Android Studio User Guide. It has a section which helps you to configure your Gradle build.

  • Thomas Knee Jun 1, 2020 @ 23:37

    Thanks for this blog post I found it very helpful. Can I ask please to explain any differences in this approach for multi test projects that don’t contain any ‘main’ classes or methods ? Do I just leave out the mainClassName ?

    Thanks

    • Petri Jun 3, 2020 @ 23:34

      Hi,

      The mainClassName property describes the name of the class that's run by the Gradle application plugin. If you don't need to use the application plugin in your project, you can simply skip the plugin declaration and leave out the line that sets the value of the mainClassName property.

      Also, it seems that the configuration of the Gradle application plugin has changed after this blog post was written. If you want to use this plugin, I recommend that you take a look at the Gradle User Guide.

  • big-pink-trunk Apr 14, 2022 @ 11:57

    Good content/article, Um, Can you write another article like this but for "multi-root-projects"? Also, is there an equivalent for "allprojects {...}" like "allrootprojects {... }"?

  • Sagar Sep 1, 2022 @ 19:05

    Thanks a lot for this tutorial!! I was pulling my hair apart while trying to combine two gradle repos but this helped a lot!

    • Petri Sep 1, 2022 @ 21:06

      You are welcome!

Leave a Reply