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.
If you are not familiar with Gradle, you should read the following blog posts before you continue reading this blog post:
- Getting Started With Gradle: Introduction helps you to install Gradle, describes the basic concepts of a Gradle build, and describes how you can add functionality to your build by using Gradle plugins.
- Getting Started With Gradle: Our First Java Project describes how you can create a Java project by using Gradle and package your application to an executable jar file.
- Getting Started With Gradle: Dependency Management describes how you can manage the dependencies of your Gradle project.
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.
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:
- 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.
- 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:
- 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.
- 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:
- Create the build.gradle file to the root directory of the core project.
- Create a Java project by applying the Java plugin.
- Ensure that the core project gets its dependencies from the central Maven2 repository.
- 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:
- Create the build.gradle file to the root directory of the app project.
- Create a Java project by applying the Java plugin.
- Ensure that the app project gets its dependencies from the central Maven2 repository.
- 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
- 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() } }
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.
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!
You are welcome! It is nice to hear that this blog post helped you to solve your problem.
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.
Hi Shyam,
Thank you for your kind words. I really appreciate them.
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.
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. :)
Could you share your views on how to maintain different build flavors across different modules ?
I am not sure what you mean by build flavors. After I consulted Google, I figured out that you might mean product flavors. Is this true?
In any case, I have never used product flavors (aka build flavors). Unfortunately this means that I cannot answer to your question. I found a few websites that might help you out:
On using gradle init, I get java.StackOverflow error.
PLease let me know the workaround/reason for this??
Which Gradle (and Java) version are you using?
Cool, thanks
You are welcome!
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.
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?
++
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
You are welcome. I am happy to hear that this blog post was useful to you.
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?
Hi Tanya,
Check out this discussion. It provides several solutions to your problem (pick the one that you like the most).
Petri,
This answers my question.
Thank you. Keep up the good work on the blog :)
Tanya
Hi Tanya,
You are welcome. I will continue writing new posts on a regular basis!
Thanks a lot.. It really helped a lot.. Nice illustration. Thanks again.
You are welcome. Also, thank you for your kind words. I really appreciate them.
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 .
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
Hi Petri,
Great tutorial. Thanks a lot.
One question, we have two modules 1. messageservice 2. appservice.
appservice has the main class.
--------------------------------
--------------------------------
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.
Got it. I guess, as both the classes are in the same package we don't need to import.
Hi,
You are right. If both classes are in the same package, you don't need to import them.
Thanks, nice post!
You are welcome!
Hi,
Thanks for the article. Do you know how to run root configuration after all sub modules are configured?
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
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.
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
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 themainClassName
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.
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 {... }"?
Thanks a lot for this tutorial!! I was pulling my hair apart while trying to combine two gradle repos but this helped a lot!
You are welcome!