When your Gradle project grows, you may want to split it into multiple subprojects to improve modularity, maintainability, and parallel execution.

This guide explains how to convert a single-project build into a multi-project build.

Why Use a Multi-Project Build?

A single-project build manages all source code and dependencies in one build.gradle(.kts) file.

In contrast, a multi-project build consists of:

  • A root project that contains shared configurations and dependencies, as defined in settings.gradle(.kts).

  • Multiple subprojects, each with its own build.gradle(.kts), dependencies, and tasks.

Step 1: Project Structure

Let’s start with a typical single-project setup, created by gradle init:

root
├── settings.gradle(.kts) (1)
└── app (2)
    ├── build.gradle(.kts)
    └── src/
1 Defines the root project settings
2 The single application module

Alternatively, your single project may look like this:

app (root) (1)
├── build.gradle(.kts)
├── settings.gradle(.kts)
└── src/
1 The project root, also acting as the application module
If your project looks like this, nest the app folder inside a new root folder before your get started.

After converting it into a multi-project build, the structure will look like this:

root
├── settings.gradle(.kts) (1)
├── app (2)
│   ├── build.gradle(.kts)
│   └── src/
└── library (3)
    ├── build.gradle(.kts)
    └── src/
1 Defines the subprojects
2 The original application module, now a subproject
3 A new library module

Step 2: Create One or More New Subprojects

Now you can create your additional projects as directories in the root. They will all become subprojects of the root.

To continue our example, we create a new subproject called library:

cd root
mkdir library

Ensure that the original project (app) remains at the same level as the new library subproject:

root
├── app (1)
│   └── some files
└── library (2)
1 The original application module
2 The newly created subproject

Move source files into their appropriate folders.

In our example, the reusable code is moved to the library subproject:

root
├── app (1)
│   └── src/
└── library (2)
    └── src/
1 The application module, containing its own source code
2 The new library module, storing reusable code

Step 3: Update settings.gradle(.kts)

Ensure that settings.gradle(.kts) is located in the root directory:

root
├── settings.gradle(.kts) (1)
├── app (2)
│   └── src/
└── library (3)
    └── src/
1 The single settings file for the entire build
2 The application module
3 The new library module

Modify settings.gradle(.kts) to include the new subprojects.

In our example, we add library to our include:

settings.gradle.kts
rootProject.name = "root"
include("app", "library")
settings.gradle
rootProject.name = "root"
include("app", "library")

There should be no other settings files.

Step 4: Create build.gradle(.kts) Files for Each Subproject

Each subproject must have its own build.gradle(.kts) file:

root
├── settings.gradle(.kts) (1)
├── app (2)
│   ├── build.gradle(.kts) (3)
│   └── src/
└── library (4)
    ├── build.gradle(.kts) (5)
    └── src/
1 Defines the subprojects
2 The application subproject
3 Defines the app build
4 The library subproject
5 Defines the library build

The app build file should look very similar to the original build file. It holds all the dependencies and configurations needed to build the app subproject.

Now, it also has a dependency on the library subproject:

app/build.gradle.kts
plugins {
    application
    id("org.jetbrains.kotlin.plugin.serialization") // Now the plugin is applied
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.google.guava:guava:33.3.1-jre")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
    implementation(project(":library")) // Uses the library module
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

application {
    mainClass = "org.example.App"
}
app/build.gradle
plugins {
    id('application')
    id("org.jetbrains.kotlin.plugin.serialization") // Now the plugin is applied
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.google.guava:guava:33.3.1-jre")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
    implementation(project(":library")) // Uses the library module
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

application {
    mainClass = "org.example.App"
}

The library build file should be new. It holds all the dependencies and configurations needed to build the library subproject:

library/build.gradle.kts
plugins {
    id("java-library")
    id("org.jetbrains.kotlin.plugin.serialization") // Now the plugin is applied
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.apache.commons:commons-lang3:3.17.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}
library/build.gradle
plugins {
    id("java-library")
    id("org.jetbrains.kotlin.plugin.serialization") // Now the plugin is applied
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.apache.commons:commons-lang3:3.17.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}

Step 5: Modify or Delete the Root build.gradle(.kts)

A build file in the root project is optional:

root
├── settings.gradle(.kts) (1)
├── build.gradle(.kts) (2)
├── app (3)
│   ├── build.gradle(.kts)
│   └── src/
└── library (4)
    ├── build.gradle(.kts)
    └── src/
1 Defines the subprojects
2 Optional (for shared configurations)
3 The application module
4 The new library module

If you need to configure shared repositories and settings, you can use the root build.gradle(.kts):

build.gradle.kts
plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.21" apply false
}

subprojects {
    repositories {
        mavenCentral()
    }
}
build.gradle
plugins {
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.21' apply false
}

subprojects {
    repositories {
        mavenCentral()
    }
}

You can take a look back at how the common 3rd party plugin called org.jetbrains.kotlin.plugin.serialization is applied in each build file.

However, shared logic should be handled using convention plugins rather than the root build file.

Summary

This process can be repeated indefinitely to add more subprojects:

root
├── settings.gradle(.kts) (1)
├── app (2)
│   ├── build.gradle(.kts)
│   └── src/
├── library-a (3)
│   ├── build.gradle(.kts)
│   └── src/
├── library-b (4)
│   ├── build.gradle(.kts)
│   └── src/
└── library-c (5)
    ├── build.gradle(.kts)
    └── src/
1 Defines the subprojects
2 The application module
3 A shared library module
4 Another library module
5 Yet Another library module

By following these steps, you can modularize your project, improve maintainability, and enable parallel execution.

If you wanted one of your libraries to be its own build, you should consider using Composite Builds (i.e., included builds) instead.