skip to content

How Kotlin Does Build Tooling

Kotlin builds run kotlinc under Gradle or Maven, and code generation happens at compile time through compiler plugins and symbol processors rather than runtime reflection. That compile-time bias is why Kotlin frameworks feel different from reflection-heavy Java ones.

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

questions

5

What actually turns your Kotlin source into runnable artifacts, and what role does Gradle (or Maven) play in that process?

level: juniorimportance: must knowfreq 70%

answer

  1. kotlinc compiles, Gradle/Maven orchestrates
  2. kotlin("jvm") plugin registers compileKotlin
  3. JVM target emits standard .class bytecode
  4. jvmTarget/languageVersion are compiler options
  5. K2 frontend in Kotlin 2.x

basics

~20 s

The Kotlin compiler, kotlinc, turns your .kt files into class files. Gradle or Maven is the build tool that calls the compiler for you, downloads libraries, and packages the output. The build tool drives the compiler.

solid answer

~30 s

Kotlin source is compiled by the Kotlin compiler, kotlinc. You almost never invoke it directly; a build tool drives it. With Gradle you apply the org.jetbrains.kotlin.jvm plugin, which registers compileKotlin tasks that invoke the compiler. Maven uses the kotlin-maven-plugin. The build tool resolves dependencies, sets the JVM target and language/API versions via compilerOptions (e.g. jvmTarget = JvmTarget.JVM_17), runs the compiler, then packages a JAR. For the JVM target, kotlinc emits standard .class bytecode that runs on any JVM; Kotlin/Native and Kotlin/JS are separate backends. The compiler itself is just a program; Gradle/Maven provide incremental compilation, caching, and dependency wiring around it.

code

kotlin · 15 lines
kotlin
plugins {
    kotlin("jvm") version "2.1.0"
}

kotlin {
    compilerOptions {
        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
        // languageVersion / apiVersion also live here
    }
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}
// ./gradlew build  -> Gradle invokes kotlinc, then packages a JAR

go deeper

for a junior

Knows kotlinc compiles and Gradle/Maven runs the build, and that JVM output is .class bytecode.

for a middle

Can name the Kotlin Gradle plugin, the compileKotlin task, and where jvmTarget/languageVersion are configured.

for a senior

Explains what the build tool adds (incremental compilation, caching, dependency resolution, plugin/KSP wiring) and the K2 frontend / multiple backends.

for a principal

Reasons about build performance trade-offs (incremental compilation correctness, daemon reuse) and consistent toolchain/jvmTarget governance across many modules.

## The pieces There are two distinct things people conflate: - **The compiler** — `kotlinc`. This is the program that reads `.kt` files and produces output. On the JVM that output is JVM **bytecode** (`.class` files); Kotlin also has **Kotlin/Native** (native binaries via LLVM) and **Kotlin/JS** (JavaScript) backends. Modern Kotlin (2.x) uses the **K2** compiler frontend. - **The build tool** — **Gradle** or **Maven**. This is what you actually run (`./gradlew build`). It resolves dependencies, decides *what* to compile, and *invokes the compiler for you*. ## How Gradle drives the compiler You apply the Kotlin Gradle plugin: ```kotlin plugins { kotlin("jvm") version "2.1.0" } kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } ``` Applying the plugin registers tasks like `compileKotlin` and `compileTestKotlin`. When you run `./gradlew build`, Gradle: 1. Resolves declared dependencies from `dependencies { }`. 2. Runs `compileKotlin`, which invokes `kotlinc` with the right classpath and options. 3. Packages the resulting `.class` files into a JAR (`jar` task). Maven does the equivalent through the `kotlin-maven-plugin` bound to the `compile` phase. ## What the build tool adds on top of the compiler - **Dependency resolution** — pulls libraries (and the Kotlin stdlib) from repositories. - **Incremental compilation** — only recompiles changed sources. - **Build caching / up-to-date checks** — skips work when inputs are unchanged. - **Wiring of compiler plugins and symbol processors** (KSP/kapt) into the compile tasks. ## Key takeaway The compiler does the *compiling*; the build tool does the *orchestrating*. `jvmTarget`, `languageVersion`, and `apiVersion` are **compiler** options you configure *through* the build tool.

  • Does the JVM run Kotlin bytecode differently from Java bytecode?
    No. For the JVM target, kotlinc emits standard JVM bytecode. The JVM cannot tell whether a class was written in Kotlin or Java; Kotlin just needs its stdlib on the classpath.
  • Where do you set the target JVM version?
    In the build script via the Kotlin plugin: kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } }. It is a compiler option passed through Gradle.

The compiler is the oven; Gradle is the chef who gathers ingredients, sets the temperature, and decides what goes in.

saying these in an interview costs you the question

  • Saying Gradle itself compiles Kotlin (it invokes kotlinc)
  • Claiming the JVM needs a special Kotlin runtime engine to run the bytecode
  • Confusing the build tool with the compiler
  • Thinking Kotlin only targets the JVM (Native and JS backends exist)
  • Setting jvmTarget in random places instead of compilerOptions

context

open as a page

Why does Kotlin recommend KSP over kapt for annotation processing, and how do they differ mechanically?

level: middleimportance: must knowfreq 65%

basics

~20 s

Both generate code at build time from annotations. kapt fakes Java stubs so old Java annotation processors work, which is slow. KSP reads Kotlin code directly through a lightweight API, so it is much faster and understands Kotlin features properly.

open as a page

What is Kotlin's opt-in mechanism for experimental APIs (e.g. @OptIn / @RequiresOptIn), and how do you satisfy it at call sites and project-wide?

level: middleimportance: should knowfreq 40%

basics

~20 s

Some Kotlin APIs are marked as experimental and may change. The compiler forces you to explicitly acknowledge that risk before using them — either by adding @OptIn at the call site or by enabling the opt-in for the whole module in the build file.

open as a page

Kotlin libraries often prefer compile-time codegen (KSP / compiler plugins) over runtime reflection. What are the engineering trade-offs of that choice?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Generating code while building makes the program start faster, run faster, and catch mistakes early because everything is decided at compile time. Reflection is more flexible and needs no extra tooling, but is slower at runtime and harder to optimize, especially for native or small binaries.

open as a page

How do Kotlin compiler plugins differ from KSP/kapt symbol processors, and what can a compiler plugin do that a processor cannot? Give examples.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Symbol processors can only read your code and generate new files. Compiler plugins hook inside the compiler and can change the actual bytecode of existing code — adding methods, rewriting bodies, or generating synthetic members. They are more powerful but more invasive.

open as a page