skip to content

Compiler

The compiler itself: the options you set, the K2 frontend, the bundled plugins that frameworks depend on, and the opt-in mechanism for experimental APIs. Questions here separate people who configure builds from people who inherit them.

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

explore

questions

25

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

Kotlin classes are final by default. Why does that break Spring and JPA, and what compiler plugins solve it?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Kotlin classes can't be subclassed unless you write 'open'. Spring and Hibernate need to subclass your classes to add behavior. The all-open and no-arg plugins make the needed classes open and give them an empty constructor automatically.

open as a page

What is the K2 compiler in Kotlin, and since which version is it the stable default?

level: juniorimportance: must knowfreq 55%

basics

~10 s

K2 is a rewritten Kotlin compiler frontend. It understands your code faster and more consistently. It became the stable default starting with Kotlin 2.0.

open as a page

What is the opt-in mechanism in Kotlin, and how do you consume an experimental API like ExperimentalCoroutinesApi?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Some Kotlin APIs are marked experimental and may change. The compiler warns or errors when you use them. To say 'I accept the risk', you add @OptIn(TheMarker::class) above your function or class.

open as a page

How do you enable kotlinx.serialization in a Kotlin Gradle project, and what two pieces do you need besides the @Serializable annotation?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Add the serialization compiler plugin in the Gradle plugins block and add the runtime library as a dependency. Then mark classes with @Serializable.

open as a page

Precisely, which problem does all-open solve and which does no-arg solve? Map kotlin-spring and kotlin-jpa onto them.

level: middleimportance: must knowfreq 65%

basics

~20 s

all-open removes 'final' so classes can be subclassed. no-arg adds an empty constructor so frameworks can create the object. kotlin-spring is an all-open preset for Spring; kotlin-jpa is a no-arg preset (plus all-open) for JPA entities.

open as a page

What exactly does the kotlin-serialization compiler plugin generate for a @Serializable class, and why is 'no reflection' a key benefit?

level: middleimportance: must knowfreq 60%

basics

~20 s

For each @Serializable class the plugin generates the code that knows how to read and write that class's fields. Because the code is built at compile time, it doesn't inspect classes with reflection at runtime, so it's faster and works where reflection is limited.

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

How would you configure all-open / no-arg for your own custom annotations rather than the Spring/JPA presets?

level: middleimportance: should knowfreq 40%

basics

~20 s

Apply the base allopen or noarg plugin and list your own annotations in its Gradle config block. Any class with those annotations then becomes open, or gets an empty constructor, just like the Spring/JPA presets do.

open as a page

What is FIR in the K2 compiler, and what role does it play?

level: middleimportance: should knowfreq 35%

basics

~10 s

FIR is the single data structure K2 uses to represent your code while analyzing it. All checking — names, types, smart casts — happens on this one model, which keeps results consistent and fast.

open as a page

K2 is described as a 'frontend rewrite.' What does the compiler frontend do, and why does keeping the backend shared matter?

level: middleimportance: should knowfreq 30%

basics

~20 s

The frontend reads and understands your code — names, types, errors. The backend turns that into runnable output like bytecode. Because K2 only rewrote the frontend, correct programs still produce the same kind of output.

open as a page

How do you author an experimental API with @RequiresOptIn, and what do its level and message parameters control?

level: middleimportance: should knowfreq 40%

basics

~20 s

You make your own annotation and mark it with @RequiresOptIn. Then you put that annotation on the API you want to flag. You can set a message and choose whether use is a warning or an error.

open as a page

How do you opt in to an experimental marker for an entire module via the compiler, and what are the trade-offs versus per-declaration @OptIn?

level: middleimportance: should knowfreq 30%

basics

~20 s

You can tell the compiler to accept a marker everywhere in the module using a build setting instead of writing @OptIn on every spot. It's convenient but you lose the per-use record of where the risky API is used.

open as a page

A teammate upgraded Kotlin but the build now fails serializing a class, or @Serializable seems ignored. How do you diagnose plugin/version issues with kotlinx.serialization?

level: middleimportance: should knowfreq 45%

basics

~10 s

Check that the serialization plugin version matches the new Kotlin version, that the plugin is actually applied, and that the runtime library is present. Mismatches or a missing plugin are the usual cause.

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

A teammate writes JPA entities as Kotlin `data class`. Why is that problematic even with kotlin-jpa applied, and what entity-modeling guidance follows?

level: seniorimportance: should knowfreq 45%

basics

~20 s

kotlin-jpa makes entities open and gives them an empty constructor, but data classes auto-generate equals/hashCode/copy from all properties. With lazy loading and mutable IDs that breaks equality and causes surprises, so plain classes are recommended for entities.

open as a page

Give a concrete example of a smart-cast or type-inference case that K2 handles better than the old compiler.

level: seniorimportance: should knowfreq 25%

basics

~20 s

K2 tracks the types of values more carefully, so smart casts work in more places — for example after combining conditions with logical operators or across local variables — where the old compiler sometimes gave up.

open as a page

Contrast propagating an opt-in requirement (re-annotating with the marker) versus using @OptIn locally. When would you choose each?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Re-annotating with the marker passes the 'please opt in' requirement on to whoever calls your code. Using @OptIn stops the requirement at your code — your callers don't have to do anything.

open as a page

Beyond @Serializable, which annotations does the serialization compiler plugin act on, and how do @SerialName, default values, and @Transient change the generated code?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The plugin reads annotations like @SerialName (rename a field or class), @Transient (skip a field), and @Required, plus default values, and bakes those rules into the generated serializer and its descriptor so encoding/decoding follow them.

open as a page

At what stage do all-open / no-arg operate, and what are the consequences for tooling, final-by-design, and modules that don't apply them?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

They run inside the Kotlin compiler while building each module, changing the generated bytecode. They aren't libraries and don't run at runtime. A module only gets the effect if it applies the plugin, and the change is invisible in your source.

open as a page

As a tech lead migrating a large Kotlin project to the K2 compiler (Kotlin 2.0), what compatibility concerns and migration steps would you plan for?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Bump to Kotlin 2.0 so K2 is the default, update plugins like kapt/serialization to K2-compatible versions, fix the small set of newly-rejected code, and run the full test/build matrix to confirm nothing broke before rolling out.

open as a page

You're designing the public API of a widely-used Kotlin library. How would you use @RequiresOptIn to manage API evolution and stability tiers, and what pitfalls would you guard against?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Use opt-in markers to label which parts of your API are still experimental, so users must consciously accept the risk. Keep stable APIs unmarked, and have a clear plan for graduating experimental APIs to stable.

open as a page

When would you choose the kotlinx.serialization compiler-plugin approach over a reflection-based library (Jackson/Gson), and what costs does compile-time codegen impose?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Choose kotlinx.serialization when you need Kotlin multiplatform, fast startup, and shrinker-friendly builds, or want first-class Kotlin support. The cost is more generated code, longer compiles, and a less mature ecosystem of format adapters than Jackson.

open as a page