Why is it usually wrong to apply a code plugin (like the Kotlin or Java plugin) directly to the root project of a multi-project build, and how does `apply false` avoid that?
answer
- root = aggregator, no source
- applying adds source sets / compile / jar / extension
- empty junk jar, cluttered tasks
- apply false = resolve without activate
- only code modules apply by id
basics
~20 sThe root is usually an aggregation project with no source. Applying a code plugin there creates source sets, compile tasks and extensions you don't want. apply false resolves the plugin without activating those on the root.
solid answer
~40 sIn a typical multi-project build the root project has no `src/` — it just aggregates subprojects and holds shared config. If you apply the Java or Kotlin plugin to the root, Gradle adds a `main`/`test` source set, `compileJava`/`compileKotlin` tasks, a `jar` task, and the `java`/`kotlin` extension to the root. You then get an empty/meaningless artifact, confusing task listings, and sometimes slower configuration. `apply false` is precisely the switch that resolves and version-pins the plugin at the root **without** applying any of that behavior. The root becomes a pure version registry; only subprojects that actually contain code apply the plugin (by id, no version) and get the source sets and tasks where they belong.
code
kotlin · 8 lines// root: pin version, keep root clean
plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.24" apply false
}
// only modules with code apply it
// lib/build.gradle.kts
plugins { id("org.jetbrains.kotlin.jvm") }go deeper
Know that the root usually has no code, so applying a code plugin there is unnecessary.
List the concrete side effects (source sets, compile tasks, jar, extension) and explain apply false as resolve-without-activate.
Compare to subprojects {} application and argue for precise per-module application over blanket application.
Reason about build hygiene, accidental empty publications, configuration time, and IDE import correctness across a large module graph.
## What 'applying' a plugin does Applying a plugin runs its `apply(project)` logic against the *current* project. For a code plugin like `java` or `org.jetbrains.kotlin.jvm` that means: - creating `main` and `test` **source sets** (expecting `src/main`, `src/test`), - registering compile tasks (`compileJava`, `compileKotlin`, …), - registering `jar`, `test`, `check`, etc., - adding the `java { }` / `kotlin { }` **extension**. ## Why that's wrong on the root The root project of a multi-project build is normally **just an aggregator** — it has no production source. Applying a code plugin there: - creates source sets pointing at directories that don't exist, - produces an empty or junk JAR if `jar` runs, - clutters `./gradlew tasks` with compile/test tasks that do nothing useful, - can slow configuration and confuse IDE import, - risks publishing an empty artifact if you also apply `maven-publish`. ## How `apply false` avoids it ```kotlin // root build.gradle.kts plugins { id("org.jetbrains.kotlin.jvm") version "1.9.24" apply false // resolve, don't activate `java-library` apply false // (core plugins too) } ``` With `apply false`, the plugin is resolved and its version pinned, but none of its source sets/tasks/extensions touch the root. The root stays clean. Each code subproject opts in: ```kotlin // lib/build.gradle.kts plugins { id("org.jetbrains.kotlin.jvm") // here the source sets/tasks belong } ``` ## Contrast with allprojects/subprojects A `subprojects { apply(plugin = "java") }` block applies the plugin to *every* subproject whether it has code or not — the same pollution problem, just pushed down. The `plugins { … apply false }` + per-module `plugins { id … }` shape is more precise: each module declares exactly what it needs.
- What concrete artifacts/tasks show up if you accidentally apply `java` to the root?A `main`/`test` source set, `compileJava`/`compileTestJava`, `jar`, `test`, `classes`, and the `java {}` extension on the root — usually producing an empty JAR and noise in `./gradlew tasks`.
- Is `apply false` needed for core plugins like `java`, or only third-party ones?It works for both. You'd rarely version core plugins (they ship with Gradle), but you can still write `\`java\` apply false` to resolve without applying — though the more common use is third-party plugins where version pinning also matters.
saying these in an interview costs you the question
- Assuming applying a code plugin to the root is harmless 'just in case' — it pollutes the root with source sets and tasks.
- Believing `apply false` is only about versions and forgetting it also prevents unwanted root behavior.