skip to content

Compiler Options & jvmTarget

jvmTarget, languageVersion and apiVersion, the jvm-default mode, and free compiler args are the knobs you actually turn. The one worth explaining is why jvmToolchain is safer than assuming the local JDK.

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

questions

5

What does the Kotlin compiler's jvmTarget option control, and what happens if you target a higher bytecode version than the JDK that actually runs your app?

level: juniorimportance: must knowfreq 70%

answer

  1. jvmTarget = bytecode class-file version
  2. Newer target on older JVM = UnsupportedClassVersionError
  3. JVM is backward compatible, not forward
  4. jvmToolchain pins JDK + aligns target
  5. major version: 52=8, 61=17, 65=21

basics

~10 s

jvmTarget sets which Java bytecode version the compiler produces, like 17 or 21. If you produce bytecode newer than the JVM running it, the app fails to start with an UnsupportedClassVersionError.

solid answer

~40 s

compilerOptions.jvmTarget (a JvmTarget enum value like "17", "21") tells kotlinc which Java bytecode class-file version to emit. It does NOT choose which JDK compiles the code — only the format of the .class files. At runtime the JVM checks each class file's major version; if a class was compiled for a newer target than the running JVM supports, you get a java.lang.UnsupportedClassVersionError at class load. Picking jvmTarget also gates which JVM features the backend can lower to. The safe modern way to keep target and toolchain consistent is jvmToolchain(n), which provisions a JDK of that version and sets both Kotlin's jvmTarget and Java's sourceCompatibility/targetCompatibility together, removing the chance of a mismatch.

code

kotlin · 9 lines
kotlin
kotlin {
    // Preferred: one knob pins the JDK and aligns jvmTarget + Java compatibility
    jvmToolchain(21)

    // Or set bytecode target explicitly (must be <= deployment JVM)
    compilerOptions {
        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
    }
}

go deeper

for a junior

Knows jvmTarget sets bytecode version and that too-new bytecode won't run on an older JVM.

for a middle

Distinguishes jvmTarget from jvmToolchain, names UnsupportedClassVersionError, knows backward-but-not-forward compatibility.

for a senior

Explains how toolchain aligns Kotlin + Java compatibility and prevents mixed-module mismatches; reasons about deployment JVM constraints.

for a principal

Sets org-wide toolchain policy, reasons about reproducible builds and class-file version 65/61 mapping across many services.

## What jvmTarget is `jvmTarget` is a Kotlin compiler option that selects the **Java bytecode (class-file) version** the compiler writes into every `.class` file it produces. Values are a fixed enum: `"1.8"`, `"9"` … `"21"`, `"24"`, etc. (the `JvmTarget` type in the Gradle DSL). It answers one question only: *what format should the output `.class` files be in?* It does **not** decide which JDK is used to run the compiler. ```kotlin // Gradle Kotlin DSL (Kotlin 2.x, modern compilerOptions block) kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) } } ``` ## Why the version matters at runtime Every `.class` file starts with a **major version** number in its header (e.g. 52 = Java 8, 61 = Java 17, 65 = Java 21). When the JVM loads a class, it refuses any class whose major version is **higher** than the JVM itself supports, throwing `java.lang.UnsupportedClassVersionError`. So bytecode compiled for target 21 simply will not load on a Java 17 JVM. Going the other way is fine: a Java 21 JVM happily runs target-17 bytecode (the JVM is backward compatible). ## jvmTarget vs jvmToolchain - **jvmTarget** = format of emitted bytecode. - **jvmToolchain(n)** = which JDK Gradle downloads/uses to compile *and* run. Setting `jvmToolchain(21)` makes Gradle provision a JDK 21 and **automatically** aligns Kotlin's `jvmTarget` and Java's `sourceCompatibility`/`targetCompatibility` to 21. Using the toolchain is the recommended approach because it removes the classic footgun of compiling with a new JDK but forgetting to set `jvmTarget`, or vice versa. ```kotlin kotlin { jvmToolchain(21) // pins JDK + aligns jvmTarget } ``` ## Common mistakes - Setting `jvmTarget` higher than your deployment JVM → `UnsupportedClassVersionError`. - Mismatching Kotlin's `jvmTarget` and Java's `targetCompatibility` in a mixed Kotlin+Java module (Gradle can warn/fail). The toolchain prevents this.

  • If you set jvmToolchain(21), do you still need to set jvmTarget manually?
    No. jvmToolchain(21) automatically sets Kotlin's jvmTarget and Java's source/targetCompatibility to 21, so an explicit jvmTarget is redundant (and would only matter if you wanted a lower bytecode than the JDK).
  • Can a Java 17 JVM run code compiled with jvmTarget 11?
    Yes. The JVM is backward compatible, so it runs any bytecode targeting its own version or lower.

jvmTarget is like writing a document in a new file format — an old reader app can't open a format newer than it knows.

saying these in an interview costs you the question

  • Thinking jvmTarget chooses which JDK compiles the code
  • Believing a newer-target class can run on an older JVM
  • Confusing jvmTarget with the Kotlin languageVersion
  • Not knowing the error is UnsupportedClassVersionError
  • Claiming JVMs are forward compatible

context

open as a page

What do allWarningsAsErrors and freeCompilerArgs do in the Kotlin compilerOptions block, and what are the tradeoffs of enabling allWarningsAsErrors in CI?

level: middleimportance: should knowfreq 40%

basics

~10 s

allWarningsAsErrors makes every compiler warning fail the build, keeping code clean. freeCompilerArgs is an escape hatch list for passing extra/experimental -X compiler flags that don't have a typed DSL property yet.

open as a page

What is the difference between languageVersion and apiVersion compiler options, and when would you set each?

level: middleimportance: should knowfreq 45%

basics

~10 s

languageVersion controls which Kotlin syntax/features the compiler accepts; apiVersion controls which version of the standard library you're allowed to call. You usually lower them to stay compatible with older consumers.

open as a page

Explain how jvmToolchain pins the JDK and how it relates to jvmTarget. In a mixed Kotlin+Java Gradle module, why is the toolchain the recommended approach?

level: seniorimportance: should knowfreq 35%

basics

~20 s

jvmToolchain(n) tells Gradle to use a specific JDK version to compile and run everything, and it automatically sets Kotlin's bytecode target and Java's compatibility to match. That keeps Kotlin and Java in one module consistent.

open as a page

What does the -Xjvm-default compiler flag do, and what are the practical differences between its modes (e.g. all, all-compatibility)?

level: seniorimportance: should knowfreq 30%

basics

~10 s

It controls whether Kotlin interface methods with bodies become real Java default methods in the bytecode, instead of being copied into a synthetic DefaultImpls class. The modes trade binary compatibility against cleaner Java interop.

open as a page