# Multi-Project Builds

version 9.0.0

As projects grow, it’s common to split them into smaller, focused modules that are built, tested, and released together. Gradle supports this through multi-project builds, allowing you to organize related codebases under a single build while keeping each module logically isolated.

## Multi-Project Layout

A multi-project build consists of a root project and one or more subprojects, all defined in a single `settings.gradle(.kts)` file. This structure supports modularization, parallel execution, and code reuse.

A typical multi-project structure looks like this:

    my-project/
    ├── settings.gradle.kts     (1)
    ├── build.gradle.kts            (2)
    ├── app/                    (3)
    │   └── build.gradle.kts        (4)
    ├── core/                   (3)
    │   └── build.gradle.kts        (5)
    └── util/                   (3)
        └── build.gradle.kts        (6)

    my-project/
    ├── settings.gradle         (1)
    ├── build.gradle        (2)
    ├── app/                    (3)
    │   └── build.gradle    (4)
    ├── core/                   (3)
    │   └── build.gradle    (5)
    └── util/                   (3)
        └── build.gradle    (6)

1 | Declares subprojects
2 | Root project build logic (optional)
3 | Subproject
4 | App module
5 | Shared core logic
6 | Utility code

Each subproject can define its own build logic, dependencies, and plugins.

In `settings.gradle(.kts)`, you include subprojects by name using `include()`. The `include()` method takes project paths as arguments:

    rootProject.name = "my-project"
    include("app", "core", "util")

    rootProject.name = 'my-project'
    include('app', 'core', 'util')

By default, a project path corresponds to the relative physical location of the project directory. For example, the path `services:api` maps to the directory `./services/api`, relative to the root project.

You can find more examples and detailed usage in the DSL reference for `Settings.include(String…​)`.

### Project Descriptors

To further describe the project architecture to Gradle, the settings file provides project descriptors.

You can modify these descriptors in the settings file at any time.

To access a descriptor, you can:

settings.gradle.kts

    include("project-a")
    println(rootProject.name)
    println(project(":project-a").name)

settings.gradle

    include('project-a')
    println rootProject.name
    println project(':project-a').name

Using this descriptor, you can change the name, project directory, and build file of a project:

settings.gradle.kts

    rootProject.name = "main"
    include("project-a")
    project(":project-a").projectDir = file("custom/my-project-a")
    project(":project-a").buildFileName = "project-a.gradle.kts"

settings.gradle

    rootProject.name = 'main'
    include('project-a')
    project(':project-a').projectDir = file('custom/my-project-a')
    project(':project-a').buildFileName = 'project-a.gradle'

Consult the ProjectDescriptor class in the API documentation for more information.

### Including projects without an existing directory

When you include a subproject in your `settings.gradle(.kts)` file, Gradle expects that the associated directory for that project exists and is writable.

| Starting with Gradle 9.0, this is enforced strictly: If a project directory is missing or read-only, the build will fail. This replaces earlier behavior where Gradle would silently allow missing project directories.

Here’s how you can create a missing directory at configuration time:

settings.gradle.kts

    include("project-without-directory")
    project(":project-without-directory").projectDir.mkdirs()

settings.gradle

    include 'project-without-directory'
    project(":project-without-directory").projectDir.mkdirs()

## Modifying a subproject path

Let’s take a hypothetical project with the following structure:

    .
    ├── settings.gradle.kts
    ├── app/
    │   ├── build.gradle.kts
    │   └── src/
    └── subs/               (1)
        ├── build.gradle.kts
        └── web             (1)
            └── my-web-module   (2)

    .
    ├── settings.gradle
    ├── app/
    │   ├── build.gradle
    │   └── src/
    └── subs/               (1)
        ├── build.gradle
        └── web             (1)
            └── my-web-module   (2)

1 | Gradle may see this as a subproject
2 | Actual intended subproject

If your `settings.gradle(.kts)` looks like this:

    include(':subs:web:my-web-module')

Gradle sees a subproject with a logical project name of `:subs:web:my-web-module` and two, possibly unintentional, other subprojects logically named:

1. `:subs`

2. `:subs:web`

This can lead to phantom build directories, especially when using `allprojects{}` or `subproject{}`.

To avoid this, you can use:

    include(':my-web-module')
    project(':my-web-module').projectDir = "subs/web/my-web-module"

So that you only end up with a single subproject named `:my-web-module`.

So, while the physical project layout is the same, the logical results are different.

## Naming recommendations

As your project grows, naming and consistency get increasingly more important.

To keep your builds maintainable, we recommend the following:

1. Keep default project names for subprojects: It is possible to configure custom project names in the settings file. However, it’s an unnecessary extra effort for the developers to track which projects belong to what folders.

2. Use lower case hyphenation for all project names: All letters are lowercase, and words are separated with a dash (`-`) character.

3. Define the root project name in the settings file: The `rootProject.name` effectively assigns a name to the build, used in reports like Build Scan. If the root project name is not set, the name will be the container directory name, which can be unstable (i.e., you can check out your project in any directory). The name will be generated randomly if the root project name is not set and checked out to a file system’s root (e.g., `/` or `C:\`).

## Declaring dependencies between Subprojects

What if one subproject depends on another? What if one subproject depends on the artifact produced by another?

This is a common use case in multi-project builds. Gradle supports this scenario with project dependencies.

### Depending on another Project

Consider a multi-project build with the following layout:

    .
    ├── api
    │   ├── src
    │   │   └──...
    │   └── build.gradle.kts
    ├── services
    │   └── person-service
    │       ├── src
    │       │   └──...
    │       └── build.gradle.kts
    ├── shared
    │   ├── src
    │   │   └──...
    │   └── build.gradle.kts
    └── settings.gradle.kts

    .
    ├── api
    │   ├── src
    │   │   └──...
    │   └── build.gradle
    ├── services
    │   └── person-service
    │       ├── src
    │       │   └──...
    │       └── build.gradle
    ├── shared
    │   ├── src
    │   │   └──...
    │   └── build.gradle
    └── settings.gradle

In this example:

1. `person-service` depends on both `api` and `shared`

2. `api` depends on `shared`

You declare these relationships using the project path, which uses colons (`:`) to indicate nesting. For example:

- `:shared` refers to the `shared` subproject

- `services:person-service` refers to a nested subproject

settings.gradle.kts

    rootProject.name = "dependencies-java"
    include("api", "shared", "services:person-service")

shared/build.gradle.kts

    plugins {
        id("java")
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation("junit:junit:4.13")
    }

api/build.gradle.kts

    plugins {
        id("java")
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation("junit:junit:4.13")
        implementation(project(":shared"))
    }

services/person-service/build.gradle.kts

    plugins {
        id("java")
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation("junit:junit:4.13")
        implementation(project(":shared"))
        implementation(project(":api"))
    }

settings.gradle

    rootProject.name = 'basic-dependencies'
    include 'api', 'shared', 'services:person-service'

shared/build.gradle

    plugins {
        id 'java'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation "junit:junit:4.13"
    }

api/build.gradle

    plugins {
        id 'java'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation "junit:junit:4.13"
        implementation project(':shared')
    }

services/person-service/build.gradle

    plugins {
        id 'java'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation "junit:junit:4.13"
        implementation project(':shared')
        implementation project(':api')
    }

For more details on project paths, consult the DSL documentation for Settings.include(String…​).

A project dependency affects both the build order and classpath:

- The required project will be built first.

- Its compiled classes and transitive dependencies are added to the consuming project’s classpath.

For example, running `./gradlew :api:compileJava` will first build `shared`, then `api`.

### Depending on Artifacts produced by another Project

Sometimes, you only need the output of a specific task from another project—not the entire project itself.

While you can create task-to-task dependencies between projects, Gradle discourages this because it creates tight coupling between tasks.

Instead, use outgoing artifacts to expose a task’s output and model it as a dependency. Gradle’s variant-aware dependency management allows one project to consume artifacts from another in a structured, on-demand way.