skip to content

What is the Gradle `plugins {}` block in a `build.gradle.kts` file, and how does it differ from the older `apply plugin` approach?

level: juniorimportance: must knowfreq 70%

answer

  1. Declarative DSL, must be first block
  2. id(...) resolves a plugin marker from gradlePluginPortal()
  3. Type-safe accessors come from eager parsing
  4. Core plugins need no version
  5. apply false = bring on classpath, apply later

basics

~20 s

The plugins {} block is where you list the build plugins a project uses, each by an id and optional version. It is the modern, recommended way and lets Gradle resolve and apply plugins efficiently.

solid answer

~40 s

`plugins {}` is the declarative plugins DSL. You write `id("org.jetbrains.kotlin.jvm") version "2.0.20"` (or `kotlin("jvm")` for the Kotlin shorthand). It must appear near the top of the script, before other statements, and its contents are restricted (no arbitrary logic). Gradle reads it eagerly to set up the plugin classpath via the `gradlePluginPortal()` plugin-management repository, giving type-safe extensions and accessors. The legacy `apply(plugin = "...")` / `buildscript { classpath ... }` approach declares dependencies imperatively, applies plugins later, gives no compile-time accessors, and shares the classpath across the build. Prefer `plugins {}`; use `apply` only for plugins not published as plugin markers or applied conditionally.

code

kotlin · 9 lines
kotlin
plugins {
    kotlin("jvm") version "2.0.20"
    id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
    application
}

application {
    mainClass.set("com.example.MainKt")
}

go deeper

for a junior

Knows plugins {} lists plugins by id + version and is the modern approach over apply.

for a middle

Explains the eager parsing, the restricted grammar, type-safe accessors, and core-vs-community version rules.

for a senior

Contrasts plugin-marker resolution vs buildscript classpath, classpath isolation, and when apply false/legacy apply is still correct.

for a principal

Reasons about build-performance and convention-plugin strategy: keeping the plugins DSL declarative so Gradle can build the plugin graph and configuration cache it.

## What the `plugins {}` block is A Gradle **plugin** packages reusable build logic (tasks, configurations, conventions). The `plugins {}` block is the **declarative plugins DSL**: a restricted block at the top of a `build.gradle.kts` (or `settings.gradle.kts`) where you declare which plugins the script applies. ```kotlin plugins { kotlin("jvm") version "2.0.20" // shorthand for id("org.jetbrains.kotlin.jvm") id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" application // built-in core plugin, no version } ``` ### Why it is special / restricted - It **must be the first block** in the script (only `buildscript {}` and `pluginManagement {}` may precede it). You cannot reference variables or call arbitrary functions inside it — only `id(...)`, `kotlin(...)`, `version`, `apply false`, and `alias(...)`. - The restriction lets Gradle **parse it eagerly**, before evaluating the rest of the script, so it knows the plugin classpath up front. This enables **type-safe accessors** (e.g. the `kotlin { }` extension becomes available with code completion). - `id(...)` matches a **plugin marker artifact** (`<id>:<id>.gradle.plugin`) resolved from the `pluginManagement` repositories — `gradlePluginPortal()` by default. ## Versus the legacy `apply` approach ```kotlin // legacy buildscript { repositories { mavenCentral() } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20") } } apply(plugin = "org.jetbrains.kotlin.jvm") ``` | Aspect | `plugins {}` | `apply` + `buildscript` | |---|---|---| | Resolution | plugin markers via `gradlePluginPortal()` | raw classpath via `buildscript` repos | | Type-safe accessors | yes | no | | Classpath isolation | per script, can be isolated | shared across build | | Restrictions | declarative only | full imperative Kotlin | Use `plugins {}` by default. Use `apply false` to bring a plugin onto the classpath in the root project but apply it later in subprojects, and use the legacy `apply` only for plugins without a published marker or for conditional application. ## Core vs community plugins Core plugins (`application`, `java`, `java-library`) ship with Gradle and need **no version**. Community plugins (Kotlin, Spring, Shadow) need a version unless it is supplied centrally via a version catalog or `pluginManagement`.

  • Why can't you write an `if` statement or read a variable inside `plugins {}`?
    Because Gradle parses the block eagerly and statically, before the rest of the script runs, so only a fixed, declarative grammar is allowed. Conditional logic must use the legacy `apply` or `apply false` + later `apply`.
  • Where does `gradlePluginPortal()` come from for the `plugins {}` block?
    From the `pluginManagement { repositories {} }` block in `settings.gradle.kts`, which defaults to `gradlePluginPortal()` if you declare none.

plugins {} is like a recipe's ingredient list at the top — declared up front so the kitchen can gather everything before cooking; apply is grabbing ingredients mid-cook.

saying these in an interview costs you the question

  • Claiming `plugins {}` and `apply plugin` are interchangeable with no difference
  • Thinking you can put arbitrary Kotlin logic inside the block
  • Saying core plugins like `application` need a version
  • Confusing dependency repositories with plugin (pluginManagement) repositories
  • Believing the block can appear anywhere in the script

context