skip to content

Gradle Kotlin DSL

Configuring Gradle in Kotlin: type-safe accessors, the plugins block, version catalogs, and the Kotlin Gradle plugin's own configuration. It is the build file you edit every week, so fluency shows.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

20

What is the difference between build.gradle and build.gradle.kts, and what do you gain by choosing the Kotlin DSL?

level: juniorimportance: must knowfreq 70%

answer

  1. .gradle = Groovy, .gradle.kts = Kotlin
  2. Kotlin DSL is compiled and type-safe
  3. IDE: autocomplete, errors, refactor, navigation
  4. '=' assignment, double quotes, parentheses
  5. Cold compile cost but same Gradle API

basics

~10 s

build.gradle uses the Groovy language; build.gradle.kts uses Kotlin. The Kotlin version is type-checked, so the IDE gives autocompletion, error highlighting, and reliable refactoring before you run the build.

solid answer

~40 s

A `build.gradle` file is written in Groovy (dynamic, loosely typed); a `build.gradle.kts` file is written in Kotlin (statically typed). Gradle compiles the `.kts` script against the project's type-safe model, so IntelliJ/Android Studio can offer real autocompletion, jump-to-source, inline documentation, and refactoring, and many mistakes surface as red compile errors instead of runtime failures. You opt in simply by naming the file with the `.kts` extension; the build is then evaluated by the Kotlin scripting engine. Trade-offs: the Kotlin DSL has a slower first/cold script-compilation step and historically a steeper learning curve, but it is the recommended default for new Gradle projects. Both DSLs target the same Gradle API, so anything expressible in Groovy is expressible in Kotlin, just with explicit types and Kotlin syntax (named arguments, `=` assignments, lambdas with receivers).

code

kotlin · 14 lines
kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
}

group = "com.example"
version = "1.0.0"

repositories { mavenCentral() }

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    testImplementation(kotlin("test"))
}

go deeper

for a junior

Knows the extension difference and that Kotlin gives IDE autocompletion and error checking.

for a middle

Explains it is a compiled, statically typed script and lists concrete IDE benefits and syntax differences.

for a senior

Discusses trade-offs (cold compile cost), migration mixing, and that both DSLs share one Gradle API.

for a principal

Frames DSL choice as a team-productivity/standardization decision and weighs compile cost vs. correctness across a large build.

## The two Gradle DSLs Gradle build scripts can be written in two languages, distinguished by file extension: - **`build.gradle`** — the **Groovy DSL**. Groovy is a dynamic JVM language; method and property names are resolved at runtime, so typos and wrong types are not caught until the build actually executes. - **`build.gradle.kts`** — the **Kotlin DSL**. `.kts` is a *Kotlin script* file. Kotlin is statically typed and compiled, so the script is checked against Gradle's typed object model before tasks run. ## What 'type-safe' buys you Because the Kotlin DSL is compiled, the IDE has a full type model of the build: - **Autocompletion** for tasks, configurations, plugin extensions (e.g. typing `java.` shows the real members of the `JavaPluginExtension`). - **Compile-time errors**: a misspelled property is red squiggle, not a 30-second build that fails at the bottom. - **Navigation & inline docs**: Ctrl/Cmd-click jumps into the plugin source; KDoc shows on hover. - **Refactoring**: rename a variable or extracted function and references update. ## Syntax differences you will see immediately ```kotlin // Kotlin DSL (build.gradle.kts) plugins { kotlin("jvm") version "2.0.0" } group = "com.example" // '=' assignment, not Groovy's space syntax version = "1.0.0" repositories { mavenCentral() } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") testImplementation(kotlin("test")) } ``` Key contrasts vs Groovy: strings always use double quotes, properties are set with `=`, methods take parentheses, and configuration blocks are **lambdas with receivers** (the `{ }` block runs against a typed receiver object). ## Opting in / interop You choose the DSL purely by the file name (`settings.gradle.kts`, `build.gradle.kts`). A single project may even mix module files of each kind during a migration. Both DSLs call the **same Gradle API**, so functional capability is identical. ## Trade-offs - The Kotlin DSL incurs a one-time **script compilation** cost (cached afterward), so a cold build is a little slower than Groovy. - It is stricter, which means slightly more ceremony but far fewer silent mistakes. - It is the **recommended default for new builds** in modern Gradle and Android Studio templates.

  • Can you mix .gradle and .gradle.kts files in the same multi-module build?
    Yes. Each module's build script is independent, so you can migrate module by module; both DSLs target the same Gradle API.
  • Why might a cold Kotlin DSL build feel slower than Groovy?
    The Kotlin script must be compiled before evaluation. The result is cached, so only the first run (or after a script change) pays the compilation cost.

Groovy DSL is like writing in a text editor with no spell-check; the Kotlin DSL is the same document in a word processor that underlines mistakes as you type.

saying these in an interview costs you the question

  • Claiming the Kotlin DSL can do things Groovy cannot (or vice versa) — both use the same API
  • Thinking you set the DSL with a setting or plugin rather than the file extension
  • Believing Groovy scripts are type-checked at edit time
  • Saying .kts is a different build tool, not just a script language

context

open as a page

How do you enable Kotlin/JVM compilation in a Gradle Kotlin DSL build, and what does applying the Kotlin Gradle Plugin actually give you?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Add the Kotlin JVM plugin in the plugins block of build.gradle.kts. That tells Gradle to compile your .kt files, sets up source folders, and adds the Kotlin standard library automatically.

open as a page

In a Kotlin Multiplatform build, how do you apply the plugin and declare which platforms (targets) your library should compile for?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Apply the kotlin("multiplatform") plugin, then inside the kotlin { } block call functions like jvm(), js(), and a native target such as linuxX64(). Each call adds a platform you compile for.

open as a page

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%

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.

open as a page

What are 'type-safe accessors' in the Gradle Kotlin DSL, where do they come from, and why might one suddenly be unavailable?

level: middleimportance: must knowfreq 55%

basics

~20 s

Type-safe accessors are generated, strongly typed shortcuts—like a java {} block or an implementation(...) configuration—that appear after a plugin is applied. They give autocompletion and compile checks. They disappear when Gradle cannot see the plugin at configuration time.

open as a page

What does `kotlin { jvmToolchain(21) }` do, and how is it different from setting jvmTarget?

level: middleimportance: must knowfreq 65%

basics

~20 s

jvmToolchain(21) tells Gradle to compile and run with a JDK 21, downloading one if needed. jvmTarget only sets the bytecode version. The toolchain controls which JDK runs the build; the target controls what class-file version is produced.

open as a page

Explain commonMain and commonTest source sets in KMP and how platform source sets relate to them.

level: middleimportance: must knowfreq 65%

basics

~10 s

commonMain holds code shared by every target; commonTest holds shared tests. Platform source sets like jvmMain depend on commonMain, so they see common code and add platform-specific code on top.

open as a page

How does expect/actual work in a KMP module, and where do you place each declaration across source sets?

level: middleimportance: must knowfreq 60%

basics

~20 s

You write an expect declaration in commonMain describing an API with no body, then provide a matching actual implementation in each platform source set. The compiler links them so common code can call the platform-specific implementation.

open as a page

What is a Gradle version catalog (`gradle/libs.versions.toml`), and how do you declare a Kotlin plugin in it and reference it with `alias(libs.plugins.x)`?

level: middleimportance: must knowfreq 60%

basics

~20 s

A version catalog is a TOML file that centralizes your dependency and plugin versions in one place. You name a plugin there, then apply it in a build script with alias(libs.plugins.someName) instead of repeating the id and version.

open as a page

In a Kotlin DSL dependencies { } block, what is the difference between implementation and api, and how do compileOnly, runtimeOnly, and testImplementation differ?

level: seniorimportance: must knowfreq 60%

basics

~20 s

implementation keeps a dependency private to your module; api exposes it to modules that depend on you. compileOnly is needed only to compile (not at runtime), runtimeOnly only at runtime (not to compile), and testImplementation only for tests.

open as a page

What is settings.gradle.kts responsible for, and what breaks if a module directory exists but is never included there?

level: middleimportance: should knowfreq 45%

basics

~20 s

settings.gradle.kts names the build and lists which subprojects (modules) belong to it via include(...). A folder with a build.gradle.kts that isn't included is invisible to Gradle—it won't build and other modules can't depend on it.

open as a page

How do you configure Kotlin compiler flags in modern KGP via the `compilerOptions { }` block, and how does that differ from the old kotlinOptions DSL?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use the kotlin { compilerOptions { } } block to set things like the language version, JVM target, and extra compiler args. It replaces the older kotlinOptions block and uses lazy Gradle Property values you set with .set(...).

open as a page

How does Gradle resolve a plugin id declared in `plugins {}` to an actual artifact, and what role does `gradlePluginPortal()` / `pluginManagement` play?

level: middleimportance: should knowfreq 40%

basics

~10 s

When you declare id("...") version "...", Gradle looks for that plugin in the plugin repositories. By default it searches the Gradle Plugin Portal. You configure these repositories in the pluginManagement block of settings.gradle.kts.

open as a page

How do the kotlin { } / java { } extension blocks and task configuration work as Kotlin lambdas-with-receivers, and how do you configure or register a task type-safely in the Kotlin DSL?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Blocks like kotlin { } and java { } are Kotlin functions whose argument is a lambda that runs against a typed object, so inside the braces you set its properties directly. To configure a task safely you use tasks.named<Type>("name") { ... } and to create one tasks.register<Type>("name") { ... }.

open as a page

How does the Kotlin JVM plugin lay out source sets, and how do you add or customize Kotlin source directories?

level: seniorimportance: should knowfreq 40%

basics

~10 s

The plugin creates main and test source sets, compiling code in src/main/kotlin and src/test/kotlin (plus the matching java folders). You can add or change directories through the sourceSets configuration in the build script.

open as a page

How do you declare dependencies in a KMP module so common code and individual platforms each get the right libraries?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Add shared libraries inside commonMain's dependencies block (they must be multiplatform), and add platform-only libraries inside that platform's source-set dependencies block, like jvmMain or androidMain.

open as a page

In a multi-module Gradle build, why do teams declare `alias(libs.plugins.kotlin.jvm) apply false` in the root project, and what error occurs if a subproject re-declares the version?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Declaring a plugin once in the root with apply false puts it on the build's classpath at a single version without activating it there. Subprojects then apply it version-free, so every module uses the same version and you avoid version conflicts.

open as a page

How does KGP wire in the Kotlin standard library, and when and how would you override that behavior?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

The Kotlin plugin adds the standard library for you automatically, matching the plugin's version, so you don't declare it. You can turn that off with a Gradle property if you need to control the version yourself.

open as a page

What is the KMP default hierarchy template, and when would you customize intermediate source sets with applyDefaultHierarchyTemplate or manual dependsOn?

level: seniorimportance: nice to knowfreq 35%

basics

~10 s

The default hierarchy template auto-creates intermediate source sets (like nativeMain, appleMain, iosMain) that group related targets so you can share code among a subset of platforms without wiring them by hand.

open as a page

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?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

kotlin("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.

open as a page