Sharing Build Logic between Subprojects
Subprojects in a multi-project build typically share some common traits.

For example, several subprojects may contain code in a particular programming language, while another subproject may be dedicated to documentation. Code quality rules apply to all the code subprojects but not the documentation subproject.
While the subprojects may share common traits, they serve different purposes. They produce different artifact types, for example:
-
public libraries - libraries that are published to some repository
-
internal libraries - libraries on which other subprojects depend
-
command line applications - applications with specific packaging requirements
-
web services - applications with specific packaging requirements
Additionally, some subprojects may be dedicated to testing.
The traits above identify a subproject’s type. In other words, a subproject’s type tells us what traits the subproject has.
Share logic using convention plugins
Gradle’s recommended way of organizing build logic is to use its plugin system.
A plugin should define the type of subproject.
In fact, Gradle core plugins are modeled the same way:
-
The Java Plugin configures a generic
java
project. -
The Java Library Plugin internally applies the Java Plugin and configures aspects specific to a Java library.
-
The Application Plugin applies and configures the Java Plugin and the Distribution Plugin.
You can compose custom build logic by applying and configuring both core and external plugins. You can create custom plugins that define new project types and configure conventions specific to your project or organization.
For each example trait above, we can write a plugin that encapsulates the logic common to the subproject of a given type:
.
├── buildSrc
│ ├── src
│ │ └──main
│ │ └──kotlin
│ │ └──myproject.java-conventions.gradle (1)
│ └── build.gradle.kts
├── api
│ ├── src
│ │ └──...
│ └── build.gradle.kts (2)
├── services
│ └── person-service
│ ├── src
│ │ └──...
│ └── build.gradle.kts (2)
├── shared
│ ├── src
│ │ └──...
│ └── build.gradle.kts (2)
└── settings.gradle.kts
1 | Create the myproject.java-conventions convention plugin. |
2 | Applies the myproject.java-conventions convention plugin. |
.
├── buildSrc
│ ├── src
│ │ └──main
│ │ └──kotlin
│ │ └──myproject.java-conventions.gradle.kts (1)
│ └── build.gradle
├── api
│ ├── src
│ │ └──...
│ └── build.gradle (2)
├── services
│ └── person-service
│ ├── src
│ │ └──...
│ └── build.gradle (2)
├── shared
│ ├── src
│ │ └──...
│ └── build.gradle (2)
└── settings.gradle
1 | Create the myproject.java-conventions convention plugin. |
2 | Applies the myproject.java-conventions convention plugin. |
Share logic in buildSrc
We recommend putting source code and tests for the convention plugins in the buildSrc
directory in the project’s root:
rootProject.name = "dependencies-java"
include("api", "shared", "services:person-service")
plugins {
id("java")
}
group = "com.example"
version = "1.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("junit:junit:4.13")
}
plugins {
id("myproject.java-conventions")
}
dependencies {
implementation(project(":shared"))
}
plugins {
id("myproject.java-conventions")
}
plugins {
id("myproject.java-conventions")
}
dependencies {
implementation(project(":shared"))
implementation(project(":api"))
}
rootProject.name = 'dependencies-java'
include 'api', 'shared', 'services:person-service'
plugins {
id 'java'
}
group = 'com.example'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation "junit:junit:4.13"
}
plugins {
id 'myproject.java-conventions'
}
dependencies {
implementation project(':shared')
}
plugins {
id 'myproject.java-conventions'
}
plugins {
id 'myproject.java-conventions'
}
dependencies {
implementation project(':shared')
implementation project(':api')
}
Consult Using buildSrc
for build logic to learn more.
Do not use cross-project configuration
An improper way to share build logic between subprojects is cross-project configuration via the subprojects {}
and allprojects {}
DSL constructs.
Avoid using subprojects {} and allprojects {} .
|
With cross-configuration, build logic can be injected into a subproject, and this is not obvious when looking at the subproject’s build script, making it harder to understand the logic of a particular subproject. In the long run, cross-configuration usually grows in complexity and becomes a burden. Cross configuration can also introduce configuration-time coupling between projects, which can prevent optimizations like configuration-on-demand from working properly.
Convention plugins versus cross-configuration
The two most common uses of cross-configuration can be better modeled using convention plugins:
-
Applying plugins or other configuration to subprojects of a certain type.
Often, the cross-configuration section will doif subproject is of type X, then configure Y
. This is equivalent to applyingX-conventions
plugin directly to a subproject. -
Extracting information from subprojects of a certain type.
This use case can be modeled using outgoing configuration variants.