During migration, how do you convert legacy `apply plugin: '...'` lines to the modern `plugins {}` block in Kotlin DSL, and what changes for plugin versions?
answer
- apply plugin -> plugins { }
- core plugin = accessor, hyphen = backticks
- id("...") version "..." inline
- declarative -> type-safe accessors
- imperative apply() for conditional/subprojects
basics
~10 sReplace each apply plugin: 'id' with an entry in a plugins {} block, e.g. id("java"). Core plugins like java or application can use the accessor form. Versions move into the plugins block via version.
solid answer
~50 sThe legacy `apply plugin: 'x'` style is imperative and resolved at apply time; the modern `plugins {}` block is **declarative** and resolved early, which is what unlocks type-safe accessors in the Kotlin DSL. So I move every plugin into a single `plugins {}` block at the top: ```kotlin plugins { java `maven-publish` id("org.springframework.boot") version "3.2.0" } ``` Key changes: - **Core plugins** get a type-safe accessor (`java`, `application`); hyphenated ids need backticks (`` `maven-publish` ``) or the `id("...")` form. - **External plugins** use `id("group.artifact") version "x"` — the version is declared inline instead of in `buildscript { dependencies }`. - The `plugins {}` block has restrictions: it must appear before other statements and can't use arbitrary logic. Plugins that genuinely need conditional/imperative application still use `apply(plugin = "...")`. Declaring plugins this way is also what makes Kotlin's generated type-safe extension accessors (like the `java {}` or `application {}` configuration blocks) available.
code
kotlin · 12 lines// Legacy Groovy:
// buildscript { dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:3.2.0' } }
// apply plugin: 'java'
// apply plugin: 'org.springframework.boot'
// apply plugin: 'maven-publish'
// Modern Kotlin DSL:
plugins {
java
id("org.springframework.boot") version "3.2.0"
`maven-publish`
}go deeper
Know that apply plugin: '...' becomes an entry in a plugins {} block.
Explain core-vs-external syntax, inline versions, and backticks for hyphens.
Connect the declarative block to type-safe accessor generation and know when apply() is still required.
Discuss standardizing plugin declaration via version catalogs and convention plugins across many modules.
## Two ways to apply a plugin Gradle has two application mechanisms: 1. **Legacy/imperative** — `apply plugin: 'java'` (Groovy) / `apply(plugin = "java")` (Kotlin). This runs as a normal statement during script evaluation. 2. **Declarative `plugins {}` block** — resolved **before** the rest of the script body executes. The declarative block matters in the Kotlin DSL because Gradle generates **type-safe model accessors** (e.g. the `application {}` extension, the `implementation(...)` configuration) from the plugins it knows are applied. Those accessors only appear if the plugin is declared in `plugins {}`. If you apply imperatively, you lose the accessor and must fall back to stringly-typed lookups like `configure<ApplicationExtension> { }` or `the<JavaPluginExtension>()`. ## Migrating the syntax ### Core plugins Groovy: `apply plugin: 'java'` → Kotlin: just `java` inside the block (a property accessor). Hyphenated names aren't valid Kotlin identifiers, so use backticks or the id form: ```kotlin plugins { java application `maven-publish` // hyphen needs backticks id("jacoco") // equivalent id-form } ``` ### External (community) plugins with versions Groovy often declared these via `buildscript { dependencies { classpath '...' } }` plus `apply plugin:`. In the modern style the version lives inline: ```kotlin plugins { id("org.springframework.boot") version "3.2.0" id("io.spring.dependency-management") version "1.1.4" } ``` With a **version catalog** you can instead write `alias(libs.plugins.spring.boot)`. ## Restrictions of the plugins block Because it's resolved early and declaratively, the `plugins {}` block: - must be (almost) the **first** thing in the script (after `buildscript`/`pluginManagement` config in settings), - cannot contain arbitrary control flow or reference script variables, - applies plugins to **this** project only. ## When you still need `apply` For conditional application (`if (someCondition) apply(plugin = "...")`), applying a plugin from a script plugin, or applying to subprojects from the root (`subprojects { apply(plugin = "java") }`), the imperative `apply(...)` form is still correct. You trade away type-safe accessors there. ## Net effect Moving to `plugins {}` is both a syntax migration and the enabler for the rest of the Kotlin DSL's static, auto-completing experience.
- Why does using plugins {} unlock type-safe accessors like application {} but apply() does not?The plugins {} block is resolved early/declaratively, so Gradle can generate type-safe Kotlin extension accessors for the applied plugins before the script body runs. Imperative apply() happens during body execution, too late for accessor generation.
- When would you still legitimately use apply(plugin = "...") in a Kotlin build?For conditional application, applying within subprojects {}/allprojects {}, or applying a plugin programmatically — cases the declarative block can't express.
saying these in an interview costs you the question
- Claiming you must keep buildscript{} classpath for every external plugin (version-in-plugins-block usually replaces it).
- Saying hyphenated core plugin ids work as bare identifiers without backticks.