skip to content

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%

answer

  1. compilerOptions { } replaces kotlinOptions { }
  2. Values are lazy Property<T> set with .set(...)
  3. Type-safe enums: JvmTarget, KotlinVersion
  4. languageVersion (syntax) vs apiVersion (libs) vs jvmTarget (bytecode)
  5. freeCompilerArgs.add(...) for raw -X flags

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(...).

solid answer

~40 s

Modern KGP exposes **`compilerOptions { }`**, available both project-wide under the `kotlin { }` extension and per-task on `KotlinCompile`. Values are **lazy Gradle `Property<T>`** objects, so you assign with `.set(...)` (or `=` with the assignment plugin). Typical settings: `jvmTarget.set(JvmTarget.JVM_21)`, `languageVersion.set(KotlinVersion.KOTLIN_2_1)`, `apiVersion.set(...)`, `allWarningsAsErrors.set(true)`, and `freeCompilerArgs.add("-Xjsr305=strict")`. These use **type-safe enums** (`JvmTarget`, `KotlinVersion`) instead of raw strings. The legacy **`kotlinOptions { }`** DSL used eager `String` properties (e.g. `jvmTarget = "21"`) and is **deprecated** in Kotlin 2.x in favor of `compilerOptions`. Configuring at the `kotlin { compilerOptions { } }` level applies to all Kotlin compilations; configuring `tasks.withType<KotlinCompile>().configureEach { compilerOptions { } }` lets you target specific tasks.

code

kotlin · 11 lines
kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

kotlin {
    compilerOptions {
        jvmTarget.set(JvmTarget.JVM_21)
        languageVersion.set(KotlinVersion.KOTLIN_2_1)
        allWarningsAsErrors.set(true)
        freeCompilerArgs.addAll("-Xjsr305=strict", "-Xjvm-default=all")
    }
}

go deeper

for a junior

Knows compiler flags go in a kotlin block and that jvmTarget can be set there.

for a middle

Uses compilerOptions with enums and .set(...), and knows kotlinOptions is the deprecated predecessor.

for a senior

Distinguishes languageVersion/apiVersion/jvmTarget, applies per-task vs project-wide config, and explains the lazy Property migration.

for a principal

Reasons about staged compiler upgrades (new kotlinc, pinned languageVersion), freeCompilerArgs governance, and configuration-avoidance implications across many modules.

## Where compiler flags live now KGP 2.x gives you a single, lazy, type-safe API: **`compilerOptions { }`**. Project-wide (applies to every Kotlin compilation): ```kotlin kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) languageVersion.set(KotlinVersion.KOTLIN_2_1) apiVersion.set(KotlinVersion.KOTLIN_2_1) allWarningsAsErrors.set(true) freeCompilerArgs.add("-Xjsr305=strict") } } ``` Per-task (override or add only on some tasks): ```kotlin tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { compilerOptions { freeCompilerArgs.add("-Xcontext-receivers") } } ``` ## Key properties - **`jvmTarget`** — emitted bytecode version, a `JvmTarget` enum (e.g. `JVM_21`). - **`languageVersion`** — the *source* language level the compiler accepts (a `KotlinVersion` enum). Lets you adopt a new compiler but keep older language semantics. - **`apiVersion`** — the maximum stdlib/library API version your code may use; must be ≤ languageVersion. - **`allWarningsAsErrors`** — fail the build on any warning. - **`freeCompilerArgs`** — a `ListProperty<String>` for raw `-X`/`-P` flags; use `.add(...)`/`.addAll(...)`. ## Lazy Property vs the old eager DSL The old DSL: ```kotlin kotlinOptions { // deprecated in 2.x jvmTarget = "21" // a plain String freeCompilerArgs += "-Xjsr305=strict" } ``` Problems it had: - **String-typed** target (`"21"`) — easy to mistype, no enum safety. - **Eager** — values resolved immediately, working against Gradle's lazy configuration/avoidance model. The new DSL fixes both: properties are **`Property<T>`** (`org.gradle.api.provider.Property`), evaluated lazily, and targets are **enums** (`JvmTarget`, `KotlinVersion`). You set them with **`.set(...)`** (or `=` if the Gradle assignment overload is enabled). `kotlinOptions` is kept as a thin deprecated bridge for migration. ## languageVersion vs apiVersion vs jvmTarget — three different things - `jvmTarget` = bytecode level (JVM concept). - `languageVersion` = which Kotlin *syntax/semantics* the compiler honors. - `apiVersion` = which Kotlin *library APIs* you may call. They are orthogonal: you might run kotlinc 2.1 with `languageVersion = 2.0` to keep old semantics while still upgrading the compiler binary.

  • What is the difference between languageVersion and apiVersion?
    languageVersion sets which Kotlin syntax/semantics the compiler accepts; apiVersion caps which stdlib/library APIs you may call. apiVersion must be ≤ languageVersion.
  • Why prefer compilerOptions over kotlinOptions?
    compilerOptions uses lazy Gradle Property values and type-safe enums, fits configuration avoidance, and kotlinOptions is deprecated in Kotlin 2.x.

saying these in an interview costs you the question

  • Setting jvmTarget to a raw string in the new DSL and expecting enum safety
  • Confusing languageVersion with jvmTarget
  • Assigning to a Property with plain = without the assignment overload, then surprised it fails
  • Thinking kotlinOptions is the current recommended API

context