Project dependencies

What if one project needs the jar produced by another project on its compile classpath? What if it also requires the transitive dependencies of the other project? Obviously this is a very common use case for Java multi-project builds. As mentioned in Project dependencies, Gradle offers project dependencies for this.

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

We have the projects shared, api and person-service. The person-service project has a dependency on the other two projects. The api project has a dependency on the shared project. It has no build script and gets nothing injected by another build script. We use the : separator to define a project path. Consult the DSL documentation of Settings.include(java.lang.String[]) for more information about defining project paths.

settings.gradle.kts
rootProject.name = "dependencies-java"
include("api", "shared", "services:person-service")
buildSrc/src/main/kotlin/myproject.java-conventions.gradle.kts
plugins {
    id("java")
}

group = "com.example"
version = "1.0"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("junit:junit:4.13")
}
api/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}

dependencies {
    implementation(project(":shared"))
}
shared/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}
services/person-service/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}

dependencies {
    implementation(project(":shared"))
    implementation(project(":api"))
}
settings.gradle
rootProject.name = 'dependencies-java'
include 'api', 'shared', 'services:person-service'
buildSrc/src/main/groovy/myproject.java-conventions.gradle
plugins {
    id 'java'
}

group = 'com.example'
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "junit:junit:4.13"
}
api/build.gradle
plugins {
    id 'myproject.java-conventions'
}

dependencies {
    implementation project(':shared')
}
shared/build.gradle
plugins {
    id 'myproject.java-conventions'
}
services/person-service/build.gradle
plugins {
    id 'myproject.java-conventions'
}

dependencies {
    implementation project(':shared')
    implementation project(':api')
}

Shared build logic is extracted into a convention plugin that is applied in the subprojects' build scripts that also define project dependencies. A project dependency is a special form of an execution dependency. It causes the other project to be built first and adds the jar with the classes of the other project to the classpath. It also adds the dependencies of the other project to the classpath. You can trigger a gradle :api:compile. First the shared project is built and then the api project is built. Project dependencies enable partial multi-project builds.

Depending on artifacts produced by another project

Project dependencies model dependencies between modules. Effectively, you are saying that you depend on the main output of another project. In a Java-based project that’s usually a JAR file.

Sometimes you may want to depend on an output produced by another task. In turn, you’ll want to make sure that the task is executed beforehand to produce that very output. Declaring a task dependency from one project to another is a poor way to model this kind of relationship and introduces unnecessary coupling. The recommended way to model such a dependency is to produce the output, mark it as an "outgoing" artifact or add it to the output of the main source set which you can depend on in the consuming project.

Let’s say you are working in a multi-project build with the two subprojects producer and consumer. The subproject producer defines a task named buildInfo that generates a properties file containing build information e.g. the project version. You can then map the task provider to its output file and Gradle will automatically establish a task dependency.

build.gradle.kts
plugins {
    id("java-library")
}

version = "1.0"

val buildInfo by tasks.registering(BuildInfo::class) {
    version = project.version.toString()
    outputFile = layout.buildDirectory.file("generated-resources/build-info.properties")
}

sourceSets {
    main {
        output.dir(buildInfo.map { it.outputFile.asFile.get().parentFile })
    }
}
build.gradle
plugins {
    id 'java-library'
}

version = '1.0'

def buildInfo = tasks.register("buildInfo", BuildInfo) {
    version = project.version
    outputFile = layout.buildDirectory.file('generated-resources/build-info.properties')
}

sourceSets {
    main {
        output.dir(buildInfo.map { it.outputFile.asFile.get().parentFile })
    }
}
buildSrc/src/main/java/BuildInfo.java
public abstract class BuildInfo extends DefaultTask {

    @Input
    public abstract Property<String> getVersion();

    @OutputFile
    public abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void create() throws IOException {
        Properties prop = new Properties();
        prop.setProperty("version", getVersion().get());
        try (OutputStream output = new FileOutputStream(getOutputFile().getAsFile().get())) {
            prop.store(output, null);
        }
    }
}

The consuming project is supposed to be able to read the properties file at runtime. Declaring a project dependency on the producing project takes care of creating the properties beforehand and making it available to the runtime classpath.

build.gradle.kts
dependencies {
    runtimeOnly(project(":producer"))
}
build.gradle
dependencies {
    runtimeOnly project(':producer')
}

In the example above, the consumer now declares a dependency on the outputs of the producer project.

Depending on the main output artifact from another project is only one example. Gradle has one of the most powerful dependency management engines that allows you to share arbitrary artifacts between projects and let Gradle build them on demand. For more details see the section on sharing outputs between projects.