I released five new sample lessons from my Test With Spring course: Introduction to Spock Framework

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.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

27 comments… add one
  • 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!

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

      Reply
      • 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.

        Reply
        • Hi Shyam,

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

          Reply
  • 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.

    Reply
    • 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. :)

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

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

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

    Reply
    • Which Gradle (and Java) version are you using?

      Reply
  • Cool, thanks

    Reply
    • You are welcome!

      Reply
  • 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.

    Reply
    • 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?

      Reply
  • ++
    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

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

      Reply
  • 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?

    Reply
    • Hi Tanya,

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

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

        Reply
        • Hi Tanya,

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

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

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

      Reply
  • 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 .

    Reply
  • 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

    Reply

Leave a Comment

Previous Post:

Next Post: