What does the `kotlin("jvm")` shorthand expand to in a `plugins {}` block, and what are common pitfalls when migrating a script to the plugins DSL with a version catalog?
answer
- kotlin("x") == id("org.jetbrains.kotlin.x")
- plugin.serialization / plugin.spring sub-ids
- Don't re-add version where root pins it
- alias() only catalog call in plugins{}
- Remove leftover buildscript classpath
basics
~10 skotlin("jvm") is a Kotlin-DSL helper that expands to id("org.jetbrains.kotlin.jvm"). It just saves typing the full id prefix. Common migration pitfalls are double-declaring versions and wrong accessor names from the catalog.
solid answer
~40 sIn the Gradle **Kotlin DSL**, `kotlin(module)` is an extension that returns `id("org.jetbrains.kotlin.$module")`, so `kotlin("jvm")` == `id("org.jetbrains.kotlin.jvm")` and `kotlin("plugin.serialization")` == `id("org.jetbrains.kotlin.plugin.serialization")`. You can still append `version "..."`. Migration pitfalls: (1) mixing `kotlin("jvm") version "x"` in a subproject when the root already pins it via `apply false` — causes an 'already requested at a different version' error; (2) catalog accessor naming — `kotlin-jvm` in TOML becomes `libs.plugins.kotlin.jvm`, and key collisions between dotted and dashed names break generation; (3) forgetting that `alias()` is the only catalog-aware call inside the restricted `plugins {}` block; (4) leaving the legacy `buildscript { classpath }` in place, which double-loads the plugin; (5) the Kotlin plugin id is `org.jetbrains.kotlin.jvm`, not the old `kotlin-platform-jvm`.
code
kotlin · 9 lines// these two lines are equivalent:
plugins { kotlin("jvm") version "2.0.20" }
plugins { id("org.jetbrains.kotlin.jvm") version "2.0.20" }
// with a catalog (version comes from libs.versions.toml):
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
}go deeper
Knows kotlin("jvm") is shorthand for the Kotlin JVM plugin id.
Expands the shorthand correctly (including plugin.serialization) and knows you can append version.
Migrates a real script: pins versions centrally, fixes accessor collisions, removes legacy buildscript, avoids double declaration.
Drives a repo-wide migration strategy to the plugins DSL + catalog, standardizing ids/accessors and preventing version-conflict regressions across modules.
## The `kotlin(...)` shorthand The Gradle **Kotlin DSL** ships an extension function used inside `plugins {}`: ```kotlin fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = id("org.jetbrains.kotlin.$module") ``` So it simply prefixes `org.jetbrains.kotlin.`: - `kotlin("jvm")` -> `id("org.jetbrains.kotlin.jvm")` - `kotlin("multiplatform")` -> `id("org.jetbrains.kotlin.multiplatform")` - `kotlin("plugin.serialization")` -> `id("org.jetbrains.kotlin.plugin.serialization")` - `kotlin("plugin.spring")` -> the kotlin-spring (all-open) plugin You can still add a version: `kotlin("jvm") version "2.0.20"`. It is purely cosmetic — same resolution as `id(...)`. ```kotlin plugins { kotlin("jvm") version "2.0.20" kotlin("plugin.serialization") version "2.0.20" } ``` ## Migration pitfalls (plugins DSL + version catalog) ### 1. Double-declaring versions If the root uses `alias(libs.plugins.kotlin.jvm) apply false`, a subproject must apply it **without** a version. Writing `kotlin("jvm") version "x"` there triggers *'plugin already requested at a different version'*. One id, one version across the build. ### 2. Catalog accessor naming TOML keys are normalized: `kotlin-jvm`, `kotlin.jvm`, `kotlin_jvm` all map to `libs.plugins.kotlin.jvm`. Defining both `kotlin-jvm` and `kotlin.jvm` is a **collision** and fails catalog generation. Pick one convention (kebab-case) and remember dash -> dotted sub-accessor. ### 3. `alias()` is the only catalog call in the block Inside the restricted `plugins {}` block you may write `alias(libs.plugins.x)` but not `id(libs.plugins.x.get().pluginId)` — the block is declarative and cannot evaluate the catalog provider imperatively. ### 4. Leftover `buildscript { classpath ... }` A migration that adds `plugins {}` but leaves the old `buildscript` classpath entry can load the Kotlin Gradle plugin twice, producing class-loader or version-conflict errors. Remove the legacy declaration. ### 5. Wrong / outdated ids The modern id is `org.jetbrains.kotlin.jvm`. Old ids like `kotlin-platform-jvm` are obsolete. Mixing `kotlin-android` vs `org.jetbrains.kotlin.android` style must match what the catalog declares. ### 6. `apply false` semantics For plugins applied only in some modules (e.g. serialization), declare `apply false` in root and apply per-module; don't apply globally if not every module needs it. ## Quick checklist - Root pins versions (catalog + `apply false`). - Subprojects apply version-free via `alias(...)` or `kotlin(...)`. - No `buildscript` classpath duplication. - Catalog keys kebab-case, accessors dotted.
- Does `kotlin("jvm")` differ in behavior from `id("org.jetbrains.kotlin.jvm")`?No. `kotlin(module)` is a Kotlin-DSL convenience that expands to `id("org.jetbrains.kotlin.$module")`; resolution and application are identical.
- Why might a build fail after migrating to `plugins {}` but keeping the old `buildscript` block?The Kotlin Gradle plugin gets loaded twice (once via buildscript classpath, once via the marker), causing version/class-loader conflicts. Remove the legacy buildscript declaration.
saying these in an interview costs you the question
- Thinking `kotlin("jvm")` is a different plugin from `org.jetbrains.kotlin.jvm`
- Re-declaring the version in subprojects when the root pins it
- Defining colliding catalog keys (`kotlin-jvm` and `kotlin.jvm`)
- Trying to call `.get()` on a catalog accessor inside `plugins {}`
- Leaving the legacy `buildscript { classpath }` after migration