Kotlin Module 1 – Start here: Kotlin 2.4 on the JVM
Install Kotlin, a JDK and an editor
Install a JDK 25 and the Kotlin 2.4.20 compiler and check both, see how JAVA_HOME and -jvm-target decide which JVM runs what, choose an editor and pin versions.
What you will learn
- Install a JDK 25 and the Kotlin 2.4.20 command-line compiler, then confirm both versions
- Explain which JVM runs kotlinc and your program, and how JAVA_HOME and -jvm-target change it
- Choose an editor, IntelliJ IDEA or Android Studio, or Visual Studio Code with the Kotlin Language Server
- Set per-project versions with mise or SDKMAN so the terminal, the editor and CI run the same toolchain
Before you start
What you’ll learn · Before you start
What you will learn
- Install a JDK 25 and the Kotlin 2.4.20 command-line compiler, then confirm both versions
- Explain which JVM runs kotlinc and your program, and how JAVA_HOME and -jvm-target change it
- Choose an editor, IntelliJ IDEA or Android Studio, or Visual Studio Code with the Kotlin Language Server
- Set per-project versions with mise or SDKMAN so the terminal, the editor and CI run the same toolchain
Before you start
On this page
Two versions decide every Kotlin run on your computer: the compiler’s, and the Java runtime’s under it. On the
computer behind this page, kotlinc -version printed kotlinc-jvm 2.4.20 (JRE 25.0.4.1+1-LTS), and after one
environment variable changed, the same command printed (JRE 27+35).
This lesson installs a JDK and the Kotlin 2.4.20 compiler and checks both, shows which JVM runs the compiler and which runs your program, chooses an editor, and pins the versions in files that travel with a project, so your terminal, your editor and your CI all build with the same toolchain.
kotlinc is a JVM program, so the JDK comes first
The Kotlin compiler for the JVM is written for the JVM: The Java Virtual Machine: the program that loads compiled class files and runs their bytecode on your computer's processor. Every JDK includes one, and it is Kotlin's main platform. More in “What Kotlin is and where it runs” and runs on one, and so does everything
it compiles. So the first install is a JDK: The Java Development Kit: a JVM together with tools such as javac and javap. kotlinc is itself a JVM program, so it needs a JDK to run as well as to run what it compiles., and the second is kotlinc. Any vendor’s JDK 25
works; the runs in this track use Eclipse Temurin 25.0.4.1 from the Adoptium project, a long-term support release.
For the compiler there are four routes:
- The zip: download
kotlin-compiler-2.4.20.zip(89,729,132 bytes) from the Kotlin 2.4.20 release on GitHub, unzip it and add itskotlinc/binfolder to yourPATH. The folder holdskotlinc,kotlinrand the compilers for JavaScript and WebAssembly. The Kotlin documentation recommends this route on Windows. - SDKMAN:
sdk install kotlin 2.4.20installs exactly that version. - Homebrew on macOS:
brew install kotlin, which installs whatever version the formula is at. - Snap on Ubuntu:
sudo snap install --classic kotlin.
Then check both installs. Step through the two rounds:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The JDK comes first. kotlinc is itself a JVM program, and so is everything it compiles, so a JDK comes first. Any vendor's JDK 25 works; the runs on this page use Eclipse Temurin 25.0.4.1, a long-term support release.
- Check java. java -version must name 25. Here it printed openjdk version "25.0.4.1" LTS. An older number, or no answer at all, means the JDK's bin folder is not first on your PATH yet.
- Install kotlinc. Unzip kotlin-compiler-2.4.20.zip from the GitHub release and put its kotlinc/bin folder on your PATH, or let a package manager install it. SDKMAN installs the exact version you name; Homebrew installs whatever version its kotlin formula is at.
- Check kotlinc. kotlinc -version names the compiler and, in brackets, the Java runtime it runs on: kotlinc-jvm 2.4.20 (JRE 25.0.4.1+1-LTS). "command not found" sends you back to the PATH.
- Which JDK runs kotlinc. The JRE in brackets is the JDK that JAVA_HOME names, or the first java on the PATH when JAVA_HOME is not set. With JAVA_HOME set to JDK 27, the same check printed (JRE 27+35).
- Ready. Both checks pass, so the computer can compile and run Kotlin. The next lesson compiles a first program into three jars and runs it.
This session ran the checks on the computer behind this page:
from session import run
# The checks after installing: which JDK and which compiler answer, where they are, and which JVM kotlinc itself
# runs on. Then JAVA_HOME names another JDK, and kotlinc runs on that one.
run("""
java -version
kotlinc -version
kotlinr -version
which java kotlinc kotlinr
echo $JAVA_HOME
export JAVA_HOME={jdk:27}
kotlinc -version
""") Output
$ java -version openjdk version "25.0.4.1" LTS OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS) OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing) $ kotlinc -version info: kotlinc-jvm 2.4.20 (JRE 25.0.4.1+1-LTS) $ kotlinr -version Kotlin version 2.4.20 (JRE 25.0.4.1+1-LTS) $ which java kotlinc kotlinr …/java/temurin-25.0.4+101.0.LTS/bin/java …/kotlin/2.4.20/kotlinc/bin/kotlinc …/kotlin/2.4.20/kotlinc/bin/kotlinr $ echo $JAVA_HOME …/java/temurin-25.0.4+101.0.LTS $ export JAVA_HOME=…/java/temurin-27.0.0+35 $ kotlinc -version info: kotlinc-jvm 2.4.20 (JRE 27+35)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 versions_session.py
java -version names the JDK, and kotlinc -version names the compiler and, in brackets, the Java runtime the
compiler itself runs on. which shows where each command lives, here in the folders of a version manager. The
kotlinc script picks its Java runtime from JAVA_HOME: An environment variable that names the folder of a JDK, the one that holds bin. kotlinc and kotlinr run on the java of the JDK it names, before any java on the PATH. when it is set, and from the first
java on the PATH otherwise: once JAVA_HOME named JDK 27, the same kotlinc reported (JRE 27+35), with
nothing else changed.
Checking java but not JAVA_HOME
java -version can say 25 while JAVA_HOME still names an older JDK, and then kotlinc and kotlinr run on the
older one. Check kotlinc -version too: the JRE in its brackets is the one the compiler runs on.
Checkpoint
Check: Installing and checking a JDK and kotlinc
A JVM runs class files up to its own version
Every class file carries a class-file version: The number in a .class file that says which Java version's bytecode it holds: 52 for Java 8, 69 for Java 25 and 70 for Java 26. A JVM refuses a number above its own., the level of the bytecode in it,
and a JVM runs class files up to its own level only. kotlinc chooses that level with
-jvm-target: The kotlinc option that sets the bytecode level of the class files it writes: 1.8 by default, and 9 to 26 in Kotlin 2.4.20. A JVM runs class files up to its own level only.: 1.8 by default, and any of 9 to 26 in Kotlin 2.4.20. This session compiles
one small program for four levels, reads the versions of three of them with javap, a tool of every JDK, and runs the
Java 26 build on two JDKs:
fun main() {
println("Running on Java ${Runtime.version().feature()}")
} from session import run
# One program compiled for four bytecode levels; javap prints the class-file version of three of them. Then the
# Java 26 build runs on JDK 25, which refuses it, and on JDK 27, which runs it.
run("""
cd target
kotlinc Hello.kt -d default
javap -v -cp default HelloKt | grep major
kotlinc Hello.kt -jvm-target 21 -d jvm21
javap -v -cp jvm21 HelloKt | grep major
kotlinc Hello.kt -jvm-target 25 -d jvm25
javap -v -cp jvm25 HelloKt | grep major
kotlinc Hello.kt -jvm-target 26 -d jvm26
kotlinr -cp jvm25 HelloKt
!kotlinr -cp jvm26 HelloKt
export JAVA_HOME={jdk:27}
kotlinr -cp jvm26 HelloKt
!kotlinc Hello.kt -jvm-target 27 -d jvm27
""") Output
$ cd target $ kotlinc Hello.kt -d default $ javap -v -cp default HelloKt | grep major major version: 52 $ kotlinc Hello.kt -jvm-target 21 -d jvm21 $ javap -v -cp jvm21 HelloKt | grep major major version: 65 $ kotlinc Hello.kt -jvm-target 25 -d jvm25 $ javap -v -cp jvm25 HelloKt | grep major major version: 69 $ kotlinc Hello.kt -jvm-target 26 -d jvm26 $ kotlinr -cp jvm25 HelloKt Running on Java 25 $ kotlinr -cp jvm26 HelloKt Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloKt has been compiled by a more recent version of the Java Runtime (class file version 70.0), this version of the Java Runtime only recognizes class file versions up to 69.0 at java.base/java.lang.ClassLoader.defineClass1(Native Method) at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:962) at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:144) at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:454) at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:367) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:557) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490) [exit status 1] $ export JAVA_HOME=…/java/temurin-27.0.0+35 $ kotlinr -cp jvm26 HelloKt Running on Java 27 $ kotlinc Hello.kt -jvm-target 27 -d jvm27 error: unknown JVM target version: 27 Supported versions: 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 target_session.py
The default build has version 52, the level of Java 8, so any JDK from 8 on can load it. -jvm-target 21 gave 65 and
-jvm-target 25 gave 69. JDK 25 ran its own level, then refused the Java 26 build: “class file version 70.0 … only
recognizes class file versions up to 69.0”, the limit the JVM specification sets for Java SE 25. JDK 27 ran the same
classes.
Target levels in Kotlin 2.4.20
The levels a compiler accepts depend on its version. Kotlin 2.4.20 knows 1.8 and 9 to 26, so it refused
-jvm-target 27 although JDK 27 is out and ran the Java 26 build; its error lists every level it accepts. Check
that list for the compiler you use.
The table sums up the four builds:
-jvm-target |
Class-file version | Runs on |
|---|---|---|
| 1.8 (the default) | 52 | JDK 8 and newer |
| 21 | 65 | JDK 21 and newer |
| 25 | 69 | JDK 25 and newer |
| 26 | 70 | JDK 26 and newer: JDK 25 refused it, JDK 27 ran it |
Key idea
Compile for the oldest JVM your code must run on: each JVM loads class files of its own level and below, and it
stops the program with UnsupportedClassVersionError at start-up when a class is newer, long after the build passed.
Checkpoint
Check: Class-file versions and -jvm-target
Choose an editor that understands Kotlin
JetBrains provides official Kotlin support for two IDEs and one editor extension. IntelliJ IDEA bundles the Kotlin plugin with each release. Android Studio, the official IDE for Android apps, is based on IntelliJ IDEA and bundles the same plugin. For Visual Studio Code there is the official Kotlin by JetBrains extension, which gives completion, navigation, debugging and diagnostics through the Kotlin language server: A program that gives an editor completion, error messages and navigation for a language through the Language Server Protocol. JetBrains' Kotlin Language Server powers its Visual Studio Code extension., a server that is still in Alpha. Other editors can use that server through the Language Server Protocol, or community plugins.
The choice depends on what you build. The Language Server supports Gradle and Maven projects for the JVM, Android projects only experimentally, and no Kotlin Multiplatform projects yet; IntelliJ IDEA and Android Studio fully support them. Answer the three questions about your work:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Editor | Official Kotlin support | Build systems | Multiplatform projects |
|---|---|---|---|
| IntelliJ IDEA Support comes from: the Kotlin plugin bundled with each release | Good official, bundled with each IntelliJ IDEA release | Good Gradle and Maven projects | Good Multiplatform projects are fully supported |
| Android Studio Support comes from: the same plugin; the IDE is based on IntelliJ IDEA | Good official: the IDE for Android apps, Kotlin bundled | Good Gradle with the Android Gradle plugin | Good Multiplatform projects are fully supported |
| Visual Studio Code Support comes from: the Kotlin by JetBrains extension, on the Kotlin Language Server | Mixed official extension, but the Language Server is in Alpha | Mixed Gradle and Maven for the JVM; Android support experimental | Poor Multiplatform support is still under development |
| Another editor with LSP Support comes from: the Kotlin Language Server or a community plugin | Mixed the Language Server works with any LSP editor; no official plugin | Mixed what the Language Server supports, nothing more | Poor Multiplatform support is still under development |
How the answers decide
- “Will you build Android apps?” Yes, Android apps: adds a caveat to IntelliJ IDEA, Visual Studio Code and Another editor with LSP. Android Studio is the official IDE for Android apps; the Language Server's Android support is still experimental.
- “Must you stay in Visual Studio Code or another editor you already use?” Yes, my editor stays: rules out IntelliJ IDEA and Android Studio and adds a caveat to Visual Studio Code and Another editor with LSP. Staying in your editor means the Kotlin Language Server, which is in Alpha, so expect gaps and report them.
- “Is it a Kotlin Multiplatform project?” Yes, several targets: rules out Visual Studio Code and Another editor with LSP. The Kotlin Language Server does not support Multiplatform projects yet; IntelliJ IDEA and Android Studio do.
In short: Android Studio for Android apps, IntelliJ IDEA for everything else, and Visual Studio Code with the Kotlin extension when you must keep your editor and your project is a JVM project. Every lesson of this track works with any of them, because each program is a file you compile and run from a terminal.
Checkpoint
Check: Choosing an editor for Kotlin
Pin the versions in files the project keeps
The terminal, the editor and a CI machine can each run a different kotlinc on a different JDK, and then the same
code behaves differently in each. The cure is to write the versions into files that are committed with the project.
A version manager reads them when you enter the project’s folder. With mise, mise use kotlin@2.4.20 and
mise use java@temurin-25.0.4+101.0.LTS write this file:
[tools]
java = "temurin-25.0.4+101.0.LTS"
kotlin = "2.4.20" Teammates and CI then run mise install to get exactly these tools, mise exec -- kotlinc -version runs a command
with them, and with mise activated in your shell, entering the folder puts them on your PATH. Write full versions,
as here: a short version such as "25" is a prefix that different computers may resolve to different builds. With
SDKMAN, the same pins live in a file named .sdkmanrc with one line per tool, java=25.0.4.1+1-tem and
kotlin=2.4.20; sdk env install installs what is missing and sdk env switches the shell to them.
Each file pins what one tool reads, and together they leave nothing to chance:
| File | Pins | Read by |
|---|---|---|
mise.toml |
kotlinc 2.4.20 and the JDK on your PATH |
mise, when you enter the folder |
.sdkmanrc |
the same two, as SDKMAN names them | sdk env |
build.gradle.kts |
the project’s Kotlin and the JDK it is built against | Gradle, on every build |
gradle/wrapper/gradle-wrapper.properties |
the Gradle version | ./gradlew |
A Gradle build pins both again for itself, which the lesson after next shows: the Kotlin version is the version of
the Kotlin Gradle plugin, and jvmToolchain(25) names the JDK the build compiles against and tests on, independent
of the JDK in your terminal. JetBrains is also building the Kotlin Toolchain, a new, declarative entry point that is
still in Alpha; it has a kotlin command of its own, which is why the runner of Kotlin 2.4.20 is called kotlinr.
Checkpoint
Check: Pinning versions per project
Practise: read a version line
The program below should read the two versions out of a line of kotlinc -version. As given, it returns null for
every line, so only the check for a line that is not a version line passes, and the program ends with status 1:
// Practice: read the compiler's version and the JRE it runs on out of one line of kotlinc -version.
fun parseKotlincVersion(line: String): Pair<String, String>? {
// Replace this line: return the two versions, or null when the line is not a kotlinc -version line.
return null
}
fun main() {
val cases =
listOf(
"info: kotlinc-jvm 2.4.20 (JRE 25.0.4.1+1-LTS)" to ("2.4.20" to "25.0.4.1+1-LTS"),
"info: kotlinc-jvm 2.4.20 (JRE 27+35)" to ("2.4.20" to "27+35"),
"info: kotlinc-jvm 2.3.21 (JRE 21.0.12.1+1-LTS)" to ("2.3.21" to "21.0.12.1+1-LTS"),
"kotlinc: command not found" to null,
)
var failed = 0
for ((line, expected) in cases) {
val actual = parseKotlincVersion(line)
if (actual != expected) failed++
val mark = if (actual == expected) "ok " else "FAIL"
println("$mark \"$line\" -> $actual")
}
println("${cases.size - failed} of ${cases.size} checks pass")
if (failed > 0) kotlin.system.exitProcess(1)
} Output (exit status 1)
FAIL "info: kotlinc-jvm 2.4.20 (JRE 25.0.4.1+1-LTS)" -> null FAIL "info: kotlinc-jvm 2.4.20 (JRE 27+35)" -> null FAIL "info: kotlinc-jvm 2.3.21 (JRE 21.0.12.1+1-LTS)" -> null ok "kotlinc: command not found" -> null 1 of 4 checks pass
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Version.kt -include-runtime -d main.jar && java -jar main.jar
Copy it, write parseKotlincVersion, and run it with kotlinc Version.kt -include-runtime -d version.jar && java -jar version.jar until all four checks pass. First hint: the compiler’s version follows kotlinc-jvm , and the JRE’s
sits between (JRE and ); substringAfter and substringBefore find both, but check first that the line has
these parts at all.
Twelve short questions follow, on installing, JVM levels, editors and pins. They begin with whatever you answer least well, and every choice explains itself:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
These three follow-up questions are the kind an interviewer asks after “how do you set up Kotlin?”. Each answer explains itself as soon as you check it:
Checkpoint
Interview round: three follow-up questions
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A Kotlin service builds and passes its tests in CI, but in production it fails at start-up with UnsupportedClassVersionError: class file version 69.0, while the runtime only recognizes versions up to 65.0. Explain the message and give two fixes, with their costs.
-
Fresher to mid
On one laptop kotlinc -version prints 2.4.21 with JRE 21.0.12.1+1-LTS; on another it prints 2.4.20 with JRE 25.0.4.1+1-LTS, and CI builds with Gradle. Small programs behave differently on the two laptops. Explain where the difference can come from and how you make every machine use the same toolchain.
Key takeaways
kotlincruns on a JVM, so a JDK comes first; check both installs withjava -versionandkotlinc -version, whose brackets name the Java runtime the compiler runs on.kotlincandkotlinrtake their JVM fromJAVA_HOMEwhen it is set and from the firstjavaon thePATHotherwise, so the same command reported JRE 25 and then JRE 27.-jvm-targetsets the class-file version, 52 by default, 65 for 21, 69 for 25 and 70 for 26, and a JVM refuses any version above its own withUnsupportedClassVersionError.- IntelliJ IDEA and Android Studio bundle the Kotlin plugin; Visual Studio Code uses the Alpha Kotlin Language Server, which supports JVM projects but not yet Multiplatform ones.
- Pin full versions in a file committed with the project,
mise.tomlor.sdkmanrc, so terminals, editors and CI run the same toolchain; a Gradle build pins its own Kotlin version and JDK as well.
Terms in this lesson
- -jvm-target
- The kotlinc option that sets the bytecode level of the class files it writes: 1.8 by default, and 9 to 26 in Kotlin 2.4.20. A JVM runs class files up to its own level only.
- class-file version
- The number in a .class file that says which Java version's bytecode it holds: 52 for Java 8, 69 for Java 25 and 70 for Java 26. A JVM refuses a number above its own.
- JAVA_HOME
- An environment variable that names the folder of a JDK, the one that holds bin. kotlinc and kotlinr run on the java of the JDK it names, before any java on the PATH.
- JDK
- The Java Development Kit: a JVM together with tools such as javac and javap. kotlinc is itself a JVM program, so it needs a JDK to run as well as to run what it compiles.
- language server
- A program that gives an editor completion, error messages and navigation for a language through the Language Server Protocol. JetBrains' Kotlin Language Server powers its Visual Studio Code extension.
References
- Kotlin command-line compiler (installation) (JetBrains (kotlinlang.org))
- Kotlin compiler options (-jvm-target and others) (JetBrains (kotlinlang.org))
- IDEs for Kotlin development (JetBrains (kotlinlang.org))
- Kotlin Language Server (JetBrains (kotlinlang.org))
- Kotlin Multiplatform quickstart (IDE support) (JetBrains (kotlinlang.org))
- The Java Virtual Machine Specification, Java SE 25 Edition, chapter 4 (the class file format) (Oracle)
- Eclipse Temurin support (LTS releases) (Eclipse Adoptium)
- SDKMAN! usage (sdk install, sdk env and .sdkmanrc) (SDKMAN!)
- Getting started with mise (mise use and mise.toml) (mise (jdx))
- Kotlin Toolchain (JetBrains (kotlin-toolchain.org))
- What's new in Kotlin 2.4.20 (the kotlinr runner command and the Kotlin Toolchain) (JetBrains (kotlinlang.org))
Report a problem with this lesson
Kept only in this browser. Your Learn progress