# Part 3: Multi-Project Builds

version 9.3.0-20260110014610+0000

Learn the basics of structuring Gradle projects using subprojects and composite builds.

In this section, you will:

- Understand Multi-Project builds

- Understand Composite Builds

- Add a subproject to your Build

- Add a build to your Build

## Step 0. Before you Begin

1. You initialized your Java app in part 1.

2. You understand the Gradle Build Lifecycle from part 2.

## Step 1. About Multi-Project Builds

Typically, builds contain multiple projects, such as shared libraries or separate applications that will be deployed in your ecosystem.

In Gradle, a multi-project build consists of:

- `settings.gradle(.kts)` file representing your Gradle build including required subprojects e.g. include("app", "model", "service")

- `build.gradle(.kts)` and source code for each subproject in corresponding subdirectories

Our build currently consists of a root project called `authoring-tutorial`, which has a single `app` subproject:

    .   (1)
    ├── app (2)
    │   ... (3)
    │   └── build.gradle.kts (4)
    └── settings.gradle.kts  (5)

1 | The `authoring-tutorial` root project
2 | The `app` subproject
3 | The `app` source code
4 | The `app` build script
5 | The optional settings file

    .   (1)
    ├── app (2)
    │   ... (3)
    │   └── build.gradle (4)
    └── settings.gradle  (5)

1 | The `authoring-tutorial` root project
2 | The `app` subproject
3 | The `app` source code
4 | The `app` build script
5 | The optional settings file

## Step 2. Add another Subproject to the Build

Imagine that our project is growing and requires a custom library to function.

Let’s create this imaginary `lib`. First, create a `lib` folder:

    mkdir lib

    cd lib

Create a file called `build.gradle(.kts)` and add the following lines to it:

lib/build.gradle.kts

    plugins {
        id("java")
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")
        testRuntimeOnly("org.junit.platform:junit-platform-launcher")
        implementation("com.google.guava:guava:32.1.1-jre")
    }

    tasks.named<Test>("test") {
        useJUnitPlatform()
    }

    tasks.register("task3"){
        println("REGISTER TASK3: This is executed during the configuration phase")
    }

    tasks.named("task3"){
        println("NAMED TASK3: This is executed during the configuration phase")
        doFirst {
            println("NAMED TASK3 - doFirst: This is executed during the execution phase")
        }
        doLast {
            println("NAMED TASK3 - doLast: This is executed during the execution phase")
        }
    }

lib/build.gradle

    plugins {
        id 'java'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
        implementation 'com.google.guava:guava:32.1.1-jre'
    }

    test {
        useJUnitPlatform()
    }

    tasks.register('task3') {
        println('REGISTER TASK3: This is executed during the configuration phase')
    }

    tasks.named('task3') {
        println('NAMED TASK3: This is executed during the configuration phase')
        doFirst {
            println('NAMED TASK3 - doFirst: This is executed during the execution phase')
        }
        doLast {
            println('NAMED TASK3 - doLast: This is executed during the execution phase')
        }
    }

Your project should look like this:

    .
    ├── app
    │   ...
    │   └── build.gradle.kts
    ├── lib
    │   └── build.gradle.kts
    └── settings.gradle.kts

    .
    ├── app
    │   ...
    │   └── build.gradle
    ├── lib
    │   └── build.gradle
    └── settings.gradle

Let’s add some code to the `lib` subproject. Create a new directory:

    mkdir -p lib/src/main/java/com/gradle

Create a Java class called `CustomLib` in a file called `CustomLib.java` with the following source code:

lib/src/main/java/com/gradle/CustomLib.java

    package com.gradle;

    public class CustomLib {
        public static String identifier = "I'm a String from a lib.";
    }

The project should now have the following file and directory structure:

    .
    ├── app
    │   ├── build.gradle.kts
    │   └── src
    │       └── main
    │           └── java
    │               └── authoring
    │                   └── tutorial
    │                       └── App.java
    ├── lib
    │   ├── build.gradle.kts
    │   └── src
    │       └── main
    │           └── java
    │               └── com
    │                   └── gradle
    │                       └── CustomLib.java
    └── settings.gradle.kts

    .
    ├── app
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           └── java
    │               └── authoring
    │                   └── tutorial
    │                       └── App.java
    ├── lib
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           └── java
    │               └── com
    │                   └── gradle
    │                       └── CustomLib.java
    └── settings.gradle

However, the `lib` subproject does not belong to the build, and you won’t be able to execute `task3`, until it is added to the `settings.gradle(.kts)` file.

To add `lib` to the build, update the `settings.gradle(.kts)` file in the root accordingly:

settings.gradle.kts

    plugins {
        id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
    }

    rootProject.name = "authoring-tutorial"

    include("app")
    include("lib") // Add lib to the build

settings.gradle

    plugins {
        id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
    }

    rootProject.name = 'authoring-tutorial'

    include('app')
    include('lib') // Add lib to the build

Let’s add the `lib` subproject as an `app` dependency in `app/build.gradle(.kts)`:

app/build.gradle.kts

    dependencies {
        implementation(project(":lib")) // Add lib as an app dependency
    }

app/build.gradle

    dependencies {
        implementation(project(':lib')) // Add lib as an app dependency
    }

Update the `app` source code so that it imports the `lib`:

app/src/main/java/authoring/tutorial/App.java

    package authoring.tutorial;

    import com.gradle.CustomLib;

    public class App {
        public String getGreeting() {
            return "CustomLib identifier is: " + CustomLib.identifier;
        }

        public static void main(String[] args) {
            System.out.println(new App().getGreeting());
        }
    }

Finally, let’s run the `app` with the command `./gradlew run`:

    $ ./gradlew run

    > Configure project :app

    > Task :app:processResources NO-SOURCE
    > Task :lib:compileJava
    > Task :lib:processResources NO-SOURCE
    > Task :lib:classes
    > Task :lib:jar
    > Task :app:compileJava
    > Task :app:classes

    > Task :app:run
    CustomLib identifier is: I'm a String from a lib.

    BUILD SUCCESSFUL in 11s
    8 actionable tasks: 6 executed, 2 up-to-date

Our build for the root project `authoring-tutorial` now includes two subprojects, `app` and `lib`. `app` depends on `lib`. You can build `lib` independent of `app`. However, to build `app`, Gradle will also build `lib`.

## Step 3. Understand Composite Builds

A composite build is simply a build that includes other builds.

Composite builds allow you to:

- Extract your build logic from your project build (and re-use it among subprojects)

- Combine builds that are usually developed independently (such as a plugin and an application)

- Decompose a large build into smaller, more isolated chunks

## Step 4. Add build to the Build

Let’s add a plugin to our build. First, create a new directory called `license-plugin` in the `gradle` directory:

    cd gradle

    mkdir license-plugin

    cd license-plugin

Once in the `gradle/license-plugin` directory, run `gradle init`. Make sure that you select the `Gradle plugin` project as well as the other options for the `init` task below:

    $ gradle init --dsl kotlin --type kotlin-gradle-plugin --project-name license

    $ gradle init --dsl groovy --type groovy-gradle-plugin --project-name license

Select defaults for any additional prompts.

Your project should look like this:

    .
    ├── app
    │   ...
    │   └── build.gradle.kts
    ├── lib
    │   ...
    │   └── build.gradle.kts
    ├── gradle
    │    ├── ...
    │    └── license-plugin
    │        ├── settings.gradle.kts
    │        └── plugin
    │            ├── gradle
    │            │   └── ....
    │            ├── src
    │            │   ├── functionalTest
    │            │   │   └── ....
    │            │   ├── main
    │            │   │   └── kotlin
    │            │   │       └── license
    │            │   │           └── LicensePlugin.kt
    │            │   └── test
    │            │       └── ...
    │            └── build.gradle.kts
    │
    └── settings.gradle.kts

    .
    ├── app
    │   ...
    │   └── build.gradle
    ├── lib
    │   ...
    │   └── build.gradle
    ├── gradle
    │    ├── ...
    │    └── license-plugin
    │        ├── settings.gradle
    │        └── plugin
    │            ├── gradle
    │            │   └── ....
    │            ├── src
    │            │   ├── functionalTest
    │            │   │   └── ....
    │            │   ├── main
    │            │   │   └── groovy
    │            │   │       └── license
    │            │   │           └── LicensePlugin.groovy
    │            │   └── test
    │            │       └── ...
    │            └── build.gradle
    │
    └── settings.gradle

Take the time to look at the `LicensePlugin.kt` or `LicensePlugin.groovy` code and the `gradle/license-plugin/settings.gradle(.kts)` file. It’s important to note that this is an entirely separate build with its own settings file and build script:

gradle/license-plugin/settings.gradle.kts

    rootProject.name = "license"
    include("plugin")

gradle/license-plugin/settings.gradle

    rootProject.name = 'license'
    include('plugin')

To add our `license-plugin` build to the root project, update the root `settings.gradle(.kts)` file accordingly:

settings.gradle.kts

    plugins {
        id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
    }

    rootProject.name = "authoring-tutorial"

    include("app")
    include("subproject")

    includeBuild("gradle/license-plugin") // Add the new build

settings.gradle

    plugins {
        id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
    }

    rootProject.name = 'running-tutorial-groovy'

    include('app')
    include('lib')

    includeBuild('gradle/license-plugin')

You can view the structure of the root project by running `./gradlew projects` in the root folder `authoring-tutorial`:

    $ ./gradlew projects

    ------------------------------------------------------------
    Root project 'authoring-tutorial'
    ------------------------------------------------------------

    Root project 'authoring-tutorial'
    +--- Project ':app'
    \--- Project ':lib'

    Included builds
    \--- Included build ':license-plugin'

Our build for the root project `authoring-tutorial` now includes two subprojects, `app` and `lib`, and another build, `license-plugin`.

When in the project root, running:

- `./gradlew build` - Builds `app` and `lib`

- `./gradlew :app:build` - Builds `app` and `lib`

- `./gradlew :lib:build` - Builds `lib` only

- `./gradlew :license-plugin:plugin:build` - Builds `license-plugin` only

There are many ways to design a project’s architecture with Gradle.

Multi-project builds are great for organizing projects with many modules such as `mobile-app`, `web-app`, `api`, `lib`, and `documentation` that have dependencies between them.

Composite (include) builds are great for separating build logic (i.e., convention plugins) or testing systems (i.e., patching a library)

Next Step: Settings File >>