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?
answer
- toolchain = which JDK compiles/runs
- jvmTarget = emitted bytecode version
- jvmToolchain infers a matching jvmTarget
- compilerOptions is lazy Property-based
- kotlinOptions is the deprecated old DSL
basics
~10 sSet 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 sThere 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 linesimport 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
Know that jvmToolchain(17) sets the JDK and that there is a way to set the target bytecode level.
Clearly separate toolchain (which JDK) from jvmTarget (emitted bytecode) and that the toolchain infers jvmTarget.
Explain the lazy compilerOptions Property API, the kotlinOptions deprecation, and the compile-new/target-old scenario.
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).