skip to content

apply false in Multi-Project Builds

Declaring a plugin once in the root build with apply false, then applying it without a version in each subproject. A very common multi-project idiom interviewers expect you to recognize on sight.

on this pageshow

questions

5

In a multi-project Gradle build, what does declaring a plugin with `apply false` in the root build script do, and why would you do it?

level: juniorimportance: must knowfreq 60%

answer

  1. resolve but don't apply
  2. pin version once at root
  3. subprojects apply by id, no version
  4. root isn't a code module
  5. puts plugin on build classpath

basics

~10 s

apply false adds the plugin to the build's classpath and resolves its version, but does NOT apply (activate) it to the root project. Subprojects can then apply it by id with no version.

solid answer

~40 s

In the root `plugins {}` block you write `id('...') version '...' apply false`. This tells Gradle to resolve the plugin (download it, put it on the build classpath) but not actually apply its tasks/extensions to the root project. The point is single-source version management: you pin the version once at the root, and each subproject that needs the plugin just writes `id('...')` with no version. This keeps all subprojects on the same plugin version, avoids repeating the version string, and avoids the error you'd get from trying to declare the same plugin version in multiple sibling projects on the same classpath. The root itself usually isn't a code module, so applying the plugin there would be wrong — `apply false` is exactly the 'resolve but don't activate' switch.

code

kotlin · 9 lines
kotlin
// root build.gradle.kts — resolve + pin, do not activate
plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.24" apply false
}

// app/build.gradle.kts — activate, version inherited
plugins {
    id("org.jetbrains.kotlin.jvm")
}

go deeper

for a junior

Know the one-liner: apply false resolves/pins the version at the root but doesn't activate the plugin there; subprojects apply by id without a version.

for a middle

Explain the resolve-vs-apply split and why repeating a version across siblings fails, motivating central declaration.

for a senior

Tie it to convention-plugin / version-catalog strategies and explain the root as a version registry that shouldn't take on plugin behavior.

for a principal

Frame it as an org-wide build-governance lever — one pinned version, reproducible builds, and how it interacts with included builds and catalogs across many repos.

## The problem `apply false` solves In a multi-project Gradle build you have a root project and several subprojects. Many subprojects want the same plugin (say the Kotlin JVM plugin), and you want them all on the **same version**. If each subproject writes `id('org.jetbrains.kotlin.jvm') version '1.9.24'`, you repeat the version everywhere and risk drift. The `plugins {}` block has a rule: a given plugin version can only be **requested with a version once** across projects that share a classpath. Declaring the version in two sibling projects fails. So you need one place to pin the version. ## How `apply false` works The `plugins {}` block does two conceptually separate things: 1. **Resolve** the plugin — find the marker/jar, download it, and put its classes on the *build script classpath*. 2. **Apply** the plugin — actually run its `apply(project)` logic, adding tasks, extensions, and conventions to *this* project. Writing `apply false` performs step 1 but skips step 2 *for the current project*. The plugin is now on the classpath and its version is fixed, but nothing is activated here. ```kotlin // root build.gradle.kts plugins { id("org.jetbrains.kotlin.jvm") version "1.9.24" apply false id("com.github.johnferguson.gradle.shadow") version "8.1.1" apply false } ``` Then in a subproject: ```kotlin // app/build.gradle.kts plugins { id("org.jetbrains.kotlin.jvm") // NO version — inherited from root } ``` Because the root already resolved and pinned the version, the subproject can apply it by id alone. Gradle finds it already on the classpath. ## Why apply it at all in the root The root project is usually an aggregation point, not a code module — applying the Kotlin plugin there would create a Kotlin source set you don't want. `apply false` lets the root act as the **version registry** without taking on the plugin's behavior. ## Where this fits in the modern setup `apply false` is the classic pre–version-catalog way to centralize plugin versions. With a version catalog you instead write `alias(libs.plugins.kotlin.jvm) apply false` in the root and `alias(libs.plugins.kotlin.jvm)` in subprojects — the `apply false` mechanic is identical, only the version source moves to `libs.versions.toml`.

  • If you remove `apply false` from the root declaration, what happens?
    The plugin gets applied to the root project too — adding its tasks/source sets/extensions to a project that usually shouldn't have them (e.g. a Kotlin source set on an aggregation-only root).
  • Can a subproject override the version inherited from the root?
    No — if the root requested the version with `apply false`, a subproject must apply it with no version. Re-requesting a version for the same plugin on the shared classpath causes an error. To diverge, that subproject needs its own buildscript-classpath setup.

Like buying one shared software license at the org level (resolving + pinning the version) but only installing the app on the laptops that actually need it (applying in subprojects).

saying these in an interview costs you the question

  • Saying `apply false` disables or removes the plugin — it still resolves and stays on the classpath; it just isn't applied here.
  • Thinking subprojects must still repeat the version after a root `apply false` declaration.

context

open as a page

A teammate adds `id('org.jetbrains.kotlin.jvm') version '1.9.24'` (with the version) to two different subprojects' build scripts and the build fails. Why, and how does the `apply false` pattern fix it?

level: middleimportance: must knowfreq 50%

basics

~20 s

Requesting the same plugin version in two projects sharing a classpath conflicts. You fix it by requesting the version once at the root with apply false, then applying by id (no version) in each subproject.

open as a page

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?

level: middleimportance: should knowfreq 30%

basics

~20 s

The 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.

open as a page

Show how the `apply false` pattern looks when versions come from a Gradle version catalog (`libs.versions.toml`), and explain what stays the same versus what changes compared to inline versions.

level: middleimportance: should knowfreq 40%

basics

~10 s

The version moves into [plugins] in libs.versions.toml. The root writes alias(libs.plugins.kotlin.jvm) apply false; subprojects write alias(libs.plugins.kotlin.jvm). The apply false resolve-but-don't-activate mechanic is unchanged.

open as a page

How does `apply false` in the root combine with precompiled convention plugins in a `buildSrc` or build-logic module to share plugin configuration across subprojects?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Convention plugins live in a separate build-logic module and apply third-party plugins by id (no version). The root apply false (or the build-logic module's dependency on the plugin marker) is what pins the version so the convention plugin can resolve it.

open as a page