skip to content

Why does an included build-logic build give better isolation and finer recompilation scope than buildSrc?

level: seniorimportance: should knowfreq 45%

answer

  1. buildSrc jar on every script classpath
  2. classpath feeds cache key → broad invalidation
  3. build-logic consumed by id → targeted
  4. modular plugin projects = narrower scope
  5. measure with config cache + build scans

basics

~20 s

buildSrc output is on every build script's classpath, so any change there can invalidate the whole build's caching. build-logic is a separate build consumed by plugin id, so changes touch only the dependent plugin paths.

solid answer

~50 s

`buildSrc` is compiled once and its classes are added to the **classpath of every build script** in the build. That makes it an invisible, global dependency: editing any buildSrc class changes the classpath that the configuration cache and build-script compilation are keyed on, so caching for configuration tends to be invalidated broadly. An included `build-logic` build is a separate build whose convention plugins are consumed **by plugin id**, only where applied. Plugins live in distinct projects, so editing one convention plugin recompiles that plugin project and reconfigures only the subprojects that apply it — a finer recompilation/reconfiguration scope. Combined with the configuration cache, this means a change in one convention plugin no longer forces a full reconfiguration of unrelated subprojects. You also gain isolation: build-logic has its own dependencies and classloading boundary rather than leaking everything onto a shared classpath.

code

kotlin · 7 lines
kotlin
// build-logic/settings.gradle.kts — modular plugin projects
rootProject.name = "build-logic"
include("java-conventions")
include("publishing-conventions")

// Editing publishing-conventions reconfigures only publishers,
// not every consumer of java-conventions.

go deeper

for a junior

Just note build-logic is more isolated because plugins are applied only where needed.

for a middle

Explain that buildSrc lands on every build script's classpath whereas build-logic is consumed by id.

for a senior

Connect classpath-as-cache-key to broad invalidation, and modular plugin projects to finer recompilation scope; acknowledge config-cache narrowing the gap.

for a principal

Weigh measured build-performance impact at monorepo scale, splitting strategy for plugin modules, and how to validate via build scans before mandating migration.

## What 'on the classpath' means for buildSrc When Gradle builds `buildSrc`, it produces a jar of classes (your convention plugins, helper code, custom tasks). That jar is then added to the **build-script classpath** of *every* project's `build.gradle(.kts)` in the main build. Two consequences follow: 1. **Global visibility / coupling.** Every build script can see every buildSrc class whether or not it should. It's an implicit dependency that doesn't appear in any `plugins {}` or `dependencies {}` block. 2. **Cache keying.** The build-script classpath is part of what Gradle hashes to decide whether compiled build scripts and the **configuration cache** entry are still valid. Change the buildSrc jar and that hash changes for everyone, so configuration-time caching is invalidated broadly. Historically this is the 'touch buildSrc, rebuild the world's configuration' pain. ## How build-logic narrows the scope An included `build-logic` build is a **separate build** composed via `includeBuild`. Convention plugins are organized into one or more **plugin projects** and consumed **by id** only in the subprojects that opt in: - A subproject that doesn't apply `com.acme.android-conventions` has no dependency on it. Editing that plugin recompiles only its plugin project and reconfigures only the subprojects that apply it. - Because consumption is by id, the dependency graph between convention plugins and consumers is **explicit**, so Gradle (and the configuration cache) can reason about which subprojects actually changed. - You can split convention logic into multiple plugin modules (java, android, publishing, quality) so a change in one doesn't ripple into the others' consumers. ## Isolation buildSrc effectively merges all build logic onto one global classpath. build-logic keeps each plugin project's dependencies inside that build with its own classloading boundary, so a library used only by one convention plugin doesn't leak onto every build script. This also reduces accidental version clashes among build-logic dependencies. ## Caveat — measure, don't assume Gradle's configuration cache has improved buildSrc behavior over versions, so the gap is smaller than it once was. The structural wins of build-logic (explicit by-id consumption, modular plugin projects, testability) are the durable reasons; the recompilation-scope improvement is real but should be validated with `--configuration-cache` and build scans rather than asserted absolutely. ```kotlin // build-logic split into focused plugin modules // build-logic/settings.gradle.kts include("java-conventions", "publishing-conventions") ``` Editing publishing-conventions then only reconfigures publishers, not every java module.

  • Why is buildSrc's classpath relevant to the configuration cache?
    The build-script classpath is part of the cache/build-script-compilation key; changing the buildSrc jar changes that key for every script, invalidating configuration-time caching broadly.
  • Has the configuration cache reduced this gap?
    Yes — newer Gradle handles buildSrc better, so the difference is smaller than historically. Validate with build scans rather than assuming a huge gap; the structural/isolation benefits of build-logic remain the stronger argument.

saying these in an interview costs you the question

  • Claiming buildSrc has zero caching — it does cache; the issue is broad invalidation when it changes.
  • Overstating the gap as if buildSrc always rebuilds everything from scratch on every run.

context