What is an included 'build-logic' build, and how does it differ from buildSrc for holding shared build logic?
answer
- buildSrc = implicit, global classpath
- build-logic = explicit includeBuild
- consumed by plugin id
- own settings.gradle, testable, publishable
- finer recompilation scope
basics
~10 sbuild-logic is a separate Gradle build (a folder with its own settings.gradle) wired in via includeBuild('build-logic'). It holds convention plugins like buildSrc but is an explicit, isolated build instead of an implicit one.
solid answer
~40 s`buildSrc` is a magic directory: Gradle compiles it automatically and puts its output on the classpath of every build script in the main build. An included `build-logic` build does the same job but is a real, standalone composite build that you opt into with `includeBuild("build-logic")` in the root `settings.gradle.kts`. Inside it you define `java-gradle-plugin` projects that publish convention plugins by id; the main build applies them via `plugins { id("my.java-conventions") }`. The key differences are isolation and incrementality: `build-logic` has its own `settings`, its own subprojects, and its plugins are consumed by plugin id rather than dumped on a global classpath. This avoids the buildSrc problem where any change invalidates the whole build's configuration cache, and it makes the convention plugins independently testable and even publishable.
code
kotlin · 12 lines// settings.gradle.kts (root)
includeBuild("build-logic")
// build-logic/build.gradle.kts
plugins { `kotlin-dsl` }
// build-logic/src/main/kotlin/com.acme.java-conventions.gradle.kts
plugins { `java-library` }
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
// app/build.gradle.kts (consumer)
plugins { id("com.acme.java-conventions") }go deeper
Know that both hold shared build logic / convention plugins; build-logic is a separate included build, buildSrc is automatic.
Explain the includeBuild wiring, consumption by plugin id, and the own-settings requirement; contrast with buildSrc's implicit global classpath.
Discuss isolation, finer recompilation scope, testability/publishability, and when migration pays off.
Frame as a build-platform decision: standardizing convention plugins across many repos via a publishable build-logic, governance, and config-cache impact at scale.
## The problem both solve In a multi-project build you want to avoid copy-pasting the same `java { }`, `dependencies { }`, and `test { }` blocks into every subproject. Gradle's answer is **convention plugins**: small plugins that encapsulate shared configuration. The question is *where the source for those plugins lives* and *how Gradle compiles and exposes it*. ## buildSrc — the implicit build `buildSrc/` is a special directory at the root of a build. Gradle **automatically** treats it as a separate build, compiles it *before* anything else, and places its compiled output on the classpath of **every** build script in the main build. You don't declare it anywhere — its mere presence wires it in. You can write convention plugins there as precompiled script plugins (`buildSrc/src/main/kotlin/my.java-conventions.gradle.kts`). Downsides: - Its classpath is **global** to all build scripts, so it's an implicit, invisible dependency. - Historically, any change to `buildSrc` invalidated a lot of caching; it sits on the critical path of configuration. - It is one monolithic unit — you can't pick which subprojects see which plugins. ## build-logic — the explicit included build An **included build** is a fully separate Gradle build composed into the main one. You create a `build-logic/` folder with its **own** `settings.gradle.kts`, then in the *root* `settings.gradle.kts` add `includeBuild("build-logic")`. Inside `build-logic` you create one or more projects applying the `java-gradle-plugin` (or `kotlin-dsl`) plugin; each registers convention plugins by **plugin id**. The main build then does `plugins { id("com.acme.java-conventions") }`. Because it's a real build: - Plugins are consumed **by id**, not by a magic global classpath — explicit and discoverable. - It has its own structure, dependencies, and tests — convention plugins become unit-testable with the `GradleRunner`/TestKit. - It can be **published** to a repository and reused across repos. - Recompilation scope is finer: editing one convention plugin doesn't necessarily invalidate everything the way a global classpath change can. ## How to wire it ```kotlin // settings.gradle.kts (root) includeBuild("build-logic") // build-logic/settings.gradle.kts rootProject.name = "build-logic" // build-logic/build.gradle.kts plugins { `kotlin-dsl` } // build-logic/src/main/kotlin/com.acme.java-conventions.gradle.kts plugins { `java-library` } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } ``` Then any subproject: `plugins { id("com.acme.java-conventions") }`. ## When to choose which For a single small build, `buildSrc` is fine and lower ceremony. For larger builds, multiple plugin groupings, a desire to test or publish convention plugins, or to reduce configuration-cache churn, migrate to `includeBuild("build-logic")` — this is the pattern Gradle's own samples and the gradle/gradle build itself recommend.
- Do you need to declare build-logic anywhere, unlike buildSrc?Yes — buildSrc is auto-wired by its name/location, but build-logic must be explicitly included with includeBuild("build-logic") in the root settings file.
- How does the main build consume convention plugins from build-logic?By plugin id in a plugins { } block, e.g. plugins { id("com.acme.java-conventions") } — not via an implicit classpath.
buildSrc is a shared toolbox bolted to every workbench in the shop — always there, always loaded. build-logic is a labeled cart you wheel up by name only where you need it.
saying these in an interview costs you the question
- Saying build-logic and buildSrc are configured identically — build-logic needs its own settings.gradle and an explicit includeBuild.
- Claiming build-logic plugins land on a global classpath like buildSrc — they're consumed by id.