Gradle
02 / 02

Multi-Module Builds & Performance

Multi-Module Builds & Performance

Version Catalogs

# gradle/libs.versions.toml — one source of truth across every module
[versions]
okhttp = "4.12.0"
junit = "5.10.0"

[libraries]
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }

# Referenced with type-safe accessors — typos caught at build-script-compile time
# build.gradle.kts:
# dependencies {
#     implementation(libs.okhttp)
#     testImplementation(libs.junit.jupiter)
# }

Build Lifecycle

Every build runs three phases: Initialization (settings.gradle.kts determines which projects participate), Configuration (every build script is evaluated to construct the task graph — this runs even if you're only executing one task in one module), and Execution (the requested tasks actually run, in dependency order, parallelizing independent branches).

Incremental Builds & the Build Cache

// A well-behaved custom task declares its real inputs/outputs, so Gradle can
// correctly mark it UP-TO-DATE (skip re-execution) when nothing relevant changed.
abstract class GenerateReportTask : DefaultTask() {
    @get:InputFile
    abstract val sourceFile: RegularFileProperty

    @get:OutputFile
    abstract val reportFile: RegularFileProperty

    @TaskAction
    fun generate() {
        reportFile.get().asFile.writeText(sourceFile.get().asFile.readText().uppercase())
    }
}
// Under-declaring an input is a classic bug: Gradle wrongly skips a task
// that actually needed to re-run, producing stale output.

// settings.gradle.kts — opt into the remote/local build cache
buildCache {
    local { isEnabled = true }
}
// A cache hit reuses another machine's (or a previous run's) output for
// identical inputs — a step further than plain incremental (same-machine) builds.

buildSrc & Shared Build Logic

buildSrc is a special module for shared build logic — convention plugins, custom task types — written in Kotlin/Java and automatically compiled and made available to every build script in the project, keeping complex logic testable and out of raw build.gradle.kts files. At larger scale, teams often move to a separate included build with published convention plugins instead, since buildSrc changes invalidate the whole build's configuration cache more aggressively.

gradle.properties & the Daemon

# gradle.properties — build-wide tuning, read as properties in scripts
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true

# ./gradlew build --no-daemon    — one-off build without the warm background JVM

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free