skip to content

How do you set the JVM target bytecode level with the Kotlin Gradle Plugin, and how does compilerOptions { jvmTarget } relate to the kotlin { jvmToolchain(...) } setting?

level: middleimportance: must knowfreq 65%

answer

  1. toolchain = which JDK compiles/runs
  2. jvmTarget = emitted bytecode version
  3. jvmToolchain infers a matching jvmTarget
  4. compilerOptions is lazy Property-based
  5. kotlinOptions is the deprecated old DSL

basics

~10 s

Set the bytecode level via kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } }. Use kotlin { jvmToolchain(17) } to also choose which JDK compiles and runs the code; the toolchain conveniently aligns the jvmTarget too.

solid answer

~40 s

There are two related-but-distinct knobs. `jvmToolchain(17)` selects which JDK Gradle uses to compile and run — Gradle provisions a JDK 17 toolchain (downloading if needed) and points the Kotlin and Java compilers and the test/run tasks at it. `compilerOptions { jvmTarget.set(JvmTarget.JVM_17) }` controls the **bytecode version** the Kotlin compiler emits (the `-jvm-target` flag). When you set `jvmToolchain`, KGP infers a matching `jvmTarget`, so for the common case configuring just the toolchain is enough and keeps Kotlin and Java aligned. You set `jvmTarget` explicitly when you want to compile with a newer JDK but target older bytecode, or to be unambiguous. In current KGP, `compilerOptions` lives on the `kotlin {}` extension (project-wide) or per-task; the older `kotlinOptions` DSL is deprecated in favor of the lazy `compilerOptions` Property-based API.

code

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

kotlin {
    // Pick the JDK that compiles AND runs the build
    jvmToolchain(17)

    // Usually inferred from the toolchain; set explicitly to be unambiguous
    compilerOptions {
        jvmTarget.set(JvmTarget.JVM_17)
    }
}

go deeper

for a junior

Know that jvmToolchain(17) sets the JDK and that there is a way to set the target bytecode level.

for a middle

Clearly separate toolchain (which JDK) from jvmTarget (emitted bytecode) and that the toolchain infers jvmTarget.

for a senior

Explain the lazy compilerOptions Property API, the kotlinOptions deprecation, and the compile-new/target-old scenario.

for a principal

Standardize toolchain/jvmTarget policy across modules via convention plugins to guarantee consistent, reproducible runtimes.

## Two different questions People conflate "which JDK builds my code" with "which bytecode version do I emit" — KGP exposes both, and they answer different questions. ### 1. Which JDK compiles and runs — the toolchain Gradle's **Java toolchains** feature lets a build declare the JDK it needs independent of the JDK that launched Gradle. With KGP you opt in via: ```kotlin kotlin { jvmToolchain(17) } ``` This makes Gradle locate (or auto-provision) a JDK 17 and wire it into `compileKotlin`, `compileJava`, `test`, and `JavaExec`/`run` tasks. The benefit is reproducibility: every developer and CI agent compiles with the same JDK regardless of what is on `PATH`. ### 2. Which bytecode is emitted — jvmTarget The Kotlin compiler's `-jvm-target` flag decides the class-file major version (e.g. `JVM_17` → bytecode 61). In modern KGP you set it through the lazy `compilerOptions` DSL: ```kotlin import org.jetbrains.kotlin.gradle.dsl.JvmTarget kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } } ``` `jvmTarget` is a Gradle `Property<JvmTarget>`, so it participates in lazy configuration and the configuration cache. ### How they relate Setting `jvmToolchain(17)` causes KGP to **infer** `jvmTarget = JVM_17` (and the equivalent Java `release`), so for the typical case you only configure the toolchain and both compilers stay aligned. You override `jvmTarget` explicitly only for advanced cases — e.g. building with JDK 21 but emitting JDK 17 bytecode for broader runtime support. If you set them inconsistently (toolchain 17 but `jvmTarget` JVM_21) you get a configuration that targets bytecode the chosen toolchain cannot run, which is a misconfiguration. ### Legacy note Older builds used `kotlinOptions { jvmTarget = "17" }` (a plain String). That `kotlinOptions` block is deprecated; prefer `compilerOptions` with the `JvmTarget` enum and `.set(...)`.

  • Why might you set jvmTarget lower than your toolchain JDK?
    To run on an older JVM in production while compiling with a newer JDK locally/CI — you emit older bytecode but still build with a modern, available toolchain.
  • What's wrong with kotlinOptions { jvmTarget = "17" }?
    It's the deprecated eager String-based DSL. The replacement is compilerOptions { jvmTarget.set(JvmTarget.JVM_17) }, which is lazy, type-safe, and configuration-cache friendly.

saying these in an interview costs you the question

  • Saying jvmToolchain and jvmTarget are the same thing.
  • Believing setting jvmTarget changes which JDK runs the build (it only changes emitted bytecode).

context