Kotlin Module 1 – Start here: Kotlin 2.4 on the JVM
Your first program with kotlinc and kotlinr
Write fun main with and without arguments, build a jar that runs alone with -include-runtime, see why Hello.kt becomes HelloKt, and run classes with kotlinr.
What you will learn
- Write fun main with and without an args parameter and run it as a self-contained jar
- Explain what -include-runtime and -d do, and why the class for Hello.kt is HelloKt
- Run compiled classes with the kotlinr runner of Kotlin 2.4.20
- Predict whether a jar runs on its own from how it was built
Before you start
What you’ll learn · Before you start
What you will learn
- Write fun main with and without an args parameter and run it as a self-contained jar
- Explain what -include-runtime and -d do, and why the class for Hello.kt is HelloKt
- Run compiled classes with the kotlinr runner of Kotlin 2.4.20
- Predict whether a jar runs on its own from how it was built
Before you start
On this page
A Kotlin program of eight lines became a jar of 5,679,509 bytes, and the same program built without one option became
a jar of 1,283 bytes that stopped at once with NoClassDefFoundError. Both jars hold the same compiled class; what
differs is whether the Kotlin runtime came along.
This lesson writes a first program with main, compiles it with kotlinc into a jar that runs on its own, opens the
jar to see the class the compiler made and why it is called HelloKt, and runs compiled code with kotlinr, the
runner command of Kotlin 2.4.20, including the two things it will not do.
fun main is where a Kotlin program starts
A Kotlin program starts at a top-level function named main. This one greets every name it is given on the command
line, and the world when it is given none:
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Namaste, world!")
}
for (name in args) {
println("Namaste, $name!")
}
} Output
Namaste, Asha! Namaste, Ravi!
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Hello.kt -include-runtime -d main.jar && java -jar main.jar Asha Ravi
The main function: The function a Kotlin program starts from, declared at the top level of a file: fun main() when it needs no command-line arguments, or fun main(args: Array<String>) when it does. takes args: Array<String>, the words that follow the program on
the command line, here Asha and Ravi. Given no words, args is an empty array rather than null, which is why
Hello.kt can test it with isEmpty(). When a program needs no arguments at all, main can leave the parameter out:
fun main() {
println("Namaste, world!")
} Output
Namaste, world!
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc World.kt -include-runtime -d main.jar && java -jar main.jar
Both forms are complete programs: no class, no static, no String[] written by hand. The line under each output
gives the command that repeats it on your computer: kotlinc builds a jar, and java -jar runs it. The next section
opens that jar.
Checkpoint
Check: fun main and its arguments
A jar runs on its own only with the runtime inside
kotlinc Hello.kt -include-runtime -d hello.jar compiles the file and writes the result to hello.jar. -d names
where the class files go, a folder or a jar: A ZIP file of compiled classes and resources. Its manifest can name the class whose main function starts the program, so that java -jar can run it.; -include-runtime adds the Kotlin runtime to the
jar, which makes it self-contained, runnable by java -jar anywhere a JVM is installed. This session builds the
program three ways:
from session import run
# Hello.kt three ways: a jar with the Kotlin runtime inside, a jar without it, and a jar with the runtime but without
# Kotlin's reflection library. unzip -p prints the manifest kotlinc wrote, and wc -c the size of each jar in bytes.
run("""
cd hello
kotlinc Hello.kt -include-runtime -d hello.jar
java -jar hello.jar Asha Ravi
java -jar hello.jar
unzip -p hello.jar META-INF/MANIFEST.MF
kotlinc Hello.kt -d lib.jar
!java -jar lib.jar Asha
kotlinc Hello.kt -include-runtime -no-reflect -d small.jar
java -jar small.jar Asha
wc -c hello.jar small.jar lib.jar
""") Output
$ cd hello
$ kotlinc Hello.kt -include-runtime -d hello.jar
$ java -jar hello.jar Asha Ravi
Namaste, Asha!
Namaste, Ravi!
$ java -jar hello.jar
Namaste, world!
$ unzip -p hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: HelloKt
$ kotlinc Hello.kt -d lib.jar
$ java -jar lib.jar Asha
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
at HelloKt.main(Hello.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
... 1 more
[exit status 1]
$ kotlinc Hello.kt -include-runtime -no-reflect -d small.jar
$ java -jar small.jar Asha
Namaste, Asha!
$ wc -c hello.jar small.jar lib.jar
5679509 hello.jar
1859527 small.jar
1283 lib.jar
7540319 total
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 jar_session.py
The manifest that kotlinc wrote names HelloKt as the Main-Class, so java -jar knows where to start;
Created-By only records the tool that made the jar. The jar built without -include-runtime has the same class and
the same manifest, yet it stopped with
NoClassDefFoundError: kotlin/jvm/internal/Intrinsics before printing anything. Step through one start of the
program to see why:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Read the manifest. java opens the jar and reads META-INF/MANIFEST.MF. kotlinc wrote Main-Class HelloKt into it, the class the program starts from.
- Load HelloKt. java asks the class loader for HelloKt, and the loader finds HelloKt.class in the jar. Both jars hold that class.
- Call main. java calls the static method main of HelloKt with the words after the jar's name, Asha here. The first call main makes goes to the Kotlin standard library, to check that args is not null.
- Find the library class. The loader now needs kotlin.jvm.internal.Intrinsics. In hello.jar it is there, because -include-runtime packed the whole Kotlin runtime in, and the program prints Namaste, Asha!
- lib.jar has no runtime. lib.jar was built without -include-runtime, so the class is not there, and nothing else is on the classpath. The JVM stops with NoClassDefFoundError and exit status 1, before printing anything.
Even this small main calls the Kotlin standard library: kotlin-stdlib: the functions and classes every Kotlin program uses, such as println and listOf. A Kotlin program on the JVM needs it on its classpath or inside its jar. first, to check that args
is not null, so a JVM can run the class only with the library on its classpath: The list of folders and jar files where the JVM looks for classes, given to java or kotlinr with -cp or -classpath.. The sizes
show what -include-runtime adds:
| Jar | Built with | Size | java -jar |
|---|---|---|---|
hello.jar |
-include-runtime |
5,679,509 bytes | runs |
small.jar |
-include-runtime -no-reflect |
1,859,527 bytes | runs |
lib.jar |
neither | 1,283 bytes | NoClassDefFoundError |
-include-runtime packs the standard library and, unless -no-reflect says otherwise, Kotlin’s reflection library
too, which is two thirds of the big jar. A program that never uses Kotlin reflection, like this one, runs the same
from the 1,859,527-byte jar.
Key idea
A jar runs on its own only when the Kotlin runtime is inside it: build programs with -include-runtime, and
libraries without it, for projects that bring the runtime themselves.
Checkpoint
Check: Jars with and without the runtime
Hello.kt compiles to a class named HelloKt
The JVM runs classes, and a Kotlin file can hold functions outside any class. So kotlinc puts the top-level
functions of each file into a class of its own, the file facade class: The JVM class kotlinc makes for the top-level functions of a file: Hello.kt becomes HelloKt, unless the file renames it with @file:JvmName., named after the
file with Kt added: Hello.kt becomes HelloKt. This session compiles into a folder instead of a jar and looks at
what the compiler wrote:
from session import run
# What kotlinc writes for Hello.kt, the class javap finds in it and the one call of main that needs the Kotlin
# standard library; then the same program in a file that names its class with @file:JvmName.
run("""
cd hello
kotlinc Hello.kt -d classes
ls classes classes/META-INF
javap -cp classes HelloKt
javap -c -cp classes HelloKt | grep Intrinsics
cd ../named
kotlinc Hello.kt -d classes
ls classes
kotlinr -cp classes Greeter Asha
""") Output
$ cd hello
$ kotlinc Hello.kt -d classes
$ ls classes classes/META-INF
classes:
HelloKt.class
META-INF
classes/META-INF:
main.kotlin_module
$ javap -cp classes HelloKt
Compiled from "Hello.kt"
public final class HelloKt {
public static final void main(java.lang.String[]);
}
$ javap -c -cp classes HelloKt | grep Intrinsics
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
$ cd ../named
$ kotlinc Hello.kt -d classes
$ ls classes
Greeter.class
META-INF
$ kotlinr -cp classes Greeter Asha
Namaste, Asha!
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 class_session.py
javap, the JDK’s class-file reader, shows HelloKt as an ordinary public final class with
public static final void main(java.lang.String[]), the signature every Java program starts from. The second javap
command found the call into the standard library that the last section’s error was about,
Intrinsics.checkNotNullParameter, the check that args is not null. META-INF/main.kotlin_module
is a small file in which the compiler records the module’s file facades for other Kotlin code.
The annotation @file:JvmName("Greeter"), at the top of the file in the named folder, gave the class another name,
and the program then started as Greeter. The name matters whenever something outside Kotlin calls the code: a
manifest, a script that starts the class, or Java code calling the functions, which a later module covers.
Checkpoint
Check: The file facade class HelloKt
kotlinr runs compiled classes, not source files
kotlinr: The runner command of Kotlin 2.4.20, which replaces kotlin as its name: it runs compiled classes, jars and .kts scripts, adding the Kotlin standard library to the classpath. is the runner command of Kotlin 2.4.20. It starts compiled code and adds the Kotlin
standard library to the classpath for you, which is why the documentation runs a jar built without the runtime with
kotlinr -classpath hello.jar HelloKt; -cp is the short form of -classpath. This session runs it four ways, then
once under its old name:
from session import run
# kotlinr runs compiled code: with -cp and a class name it adds the Kotlin standard library to the classpath; given a
# jar on its own, it runs that jar as it is. It does not compile source files. kotlin, the runner's old name, is
# still in the 2.4.20 distribution.
run("""
cd hello
kotlinc Hello.kt -d lib.jar
kotlinr -cp lib.jar HelloKt Asha Ravi
!kotlinr lib.jar Asha
kotlinc Hello.kt -include-runtime -d hello.jar
kotlinr hello.jar Asha
!kotlinr Hello.kt
kotlin -cp lib.jar HelloKt Asha
""") Output
$ cd hello $ kotlinc Hello.kt -d lib.jar $ kotlinr -cp lib.jar HelloKt Asha Ravi Namaste, Asha! Namaste, Ravi! $ kotlinr lib.jar Asha Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics at HelloKt.main(Hello.kt) Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:377) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:557) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490) ... 1 more [exit status 1] $ kotlinc Hello.kt -include-runtime -d hello.jar $ kotlinr hello.jar Asha Namaste, Asha! $ kotlinr Hello.kt error: could not find or load main class Hello.kt [exit status 1] $ kotlin -cp lib.jar HelloKt Asha Namaste, Asha!
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 runner_session.py
kotlin became kotlinr in Kotlin 2.4.20
Kotlin 2.4.20 renamed the runner from kotlin to kotlinr, to avoid a clash with the kotlin command of the
Kotlin Toolchain. The old name still starts the runner in the 2.4.20 distribution, and the last command above ran
without a warning, although the release notes say the runner warns about the old name. Prefer kotlinr in new
scripts.
The table sums up what each command did:
| Command | What happened |
|---|---|
kotlinr -cp lib.jar HelloKt Asha Ravi |
ran: kotlinr added the standard library |
kotlinr lib.jar Asha |
NoClassDefFoundError: a jar given alone runs as it is |
kotlinr hello.jar Asha |
ran: this jar carries the runtime |
kotlinr Hello.kt |
could not find or load main class Hello.kt |
kotlin -cp lib.jar HelloKt Asha |
ran: the old name still works in 2.4.20 |
So kotlinr takes a class name and a classpath, or a jar that can run alone, and it never compiles: Hello.kt is a
source file, not a class, so it found no class by that name. Compile with kotlinc first. The runner also starts
Kotlin scripts, files ending in .kts whose statements run without a main, which the last lesson of this module
covers.
Running a source file with the runner
kotlinr Hello.kt fails with could not find or load main class Hello.kt, because the runner looks for a compiled
class of that name. Compile first, kotlinc Hello.kt -d lib.jar, then run kotlinr -cp lib.jar HelloKt.
Checkpoint
Check: What kotlinr runs
Practise: greet the names
The program below checks a function that should return the greetings of Hello.kt as a list, one per name, or the
greeting to the world when there are no names. As given, it returns an empty list, so all three checks fail and the
program ends with status 1:
// Practice: one greeting per name, or a greeting to the world when there are no names.
fun greetings(args: List<String>): List<String> {
// Replace this line with the rule of the lesson's program.
return emptyList()
}
fun main() {
val cases =
listOf(
emptyList<String>() to listOf("Namaste, world!"),
listOf("Asha") to listOf("Namaste, Asha!"),
listOf("Asha", "Ravi", "Meera") to listOf("Namaste, Asha!", "Namaste, Ravi!", "Namaste, Meera!"),
)
var failed = 0
for ((names, expected) in cases) {
val actual = greetings(names)
if (actual != expected) failed++
val mark = if (actual == expected) "ok " else "FAIL"
println("$mark $names -> $actual")
}
println("${cases.size - failed} of ${cases.size} checks pass")
if (failed > 0) kotlin.system.exitProcess(1)
} Output (exit status 1)
FAIL [] -> [] FAIL [Asha] -> [] FAIL [Asha, Ravi, Meera] -> [] 0 of 3 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 Greetings.kt -include-runtime -d main.jar && java -jar main.jar
Copy it, write greetings, and run it with kotlinc Greetings.kt -include-runtime -d greetings.jar && java -jar greetings.jar until all three checks pass. First hint: Hello.kt already has both cases, an if for no names and a
for loop for each name; map turns a list of names into a list of greetings in one call.
Last, eight questions on main, jars, facade classes and kotlinr. Your weakest idea comes up first; pick an answer to see its reason at once:
Practice that adapts to you
Practise this lesson
8 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
Fresher · Why does a file named Hello.kt compile to a class called HelloKt, and how do you change the name?
The JVM runs classes, but Kotlin lets a file declare functions at the top level, outside any class. So the compiler
puts every top-level function and property of a file into one generated class, the file facade, named after the file
with Kt added: Hello.kt gives HelloKt, with main as a public static final method. That name is what a jar’s
Main-Class, a launch script or Java code has to use. To choose another name, put @file:JvmName("Greeter") at the
top of the file, before the package line; the class is then Greeter. Renaming matters most when Java code calls the
functions, because the Java side sees the facade class’s name.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A teammate's small Kotlin tool runs in the IDE, but java -jar tool.jar on a server stops with NoClassDefFoundError: kotlin/jvm/internal/Intrinsics before printing anything. Explain what went wrong and how you would fix and prevent it.
-
Mid to senior
A 40-line command-line tool ships as a 5.7 MB jar built with -include-runtime. Operations want it smaller, and another team wants to call its functions from their Kotlin service. How do you package it for both?
Key takeaways
- A Kotlin program starts at a top-level
fun main, withargs: Array<String>when it needs the command line and without it when it does not. -dnames where the classes go, a folder or a jar, and-include-runtimemakes a jar run alone: 5,679,509 bytes with the runtime, 1,859,527 with-no-reflect, and 1,283 bytes without the runtime, which failed withNoClassDefFoundError.- Even a tiny
maincalls the standard library, to check its parameter, so a Kotlin class runs only with the library in its jar or on its classpath. - The top-level functions of
Hello.ktlive in the file facade classHelloKt;@file:JvmNamegives it another name. kotlinr, the runner of Kotlin 2.4.20, runs a class with the standard library added or a self-contained jar, and never compiles a source file; the oldkotlincommand still works.
Terms in this lesson
- classpath
- The list of folders and jar files where the JVM looks for classes, given to java or kotlinr with -cp or -classpath.
- file facade class
- The JVM class kotlinc makes for the top-level functions of a file: Hello.kt becomes HelloKt, unless the file renames it with @file:JvmName.
- jar
- A ZIP file of compiled classes and resources. Its manifest can name the class whose main function starts the program, so that java -jar can run it.
- Kotlin standard library
- kotlin-stdlib: the functions and classes every Kotlin program uses, such as println and listOf. A Kotlin program on the JVM needs it on its classpath or inside its jar.
- kotlinr
- The runner command of Kotlin 2.4.20, which replaces kotlin as its name: it runs compiled classes, jars and .kts scripts, adding the Kotlin standard library to the classpath.
- main function
- The function a Kotlin program starts from, declared at the top level of a file: fun main() when it needs no command-line arguments, or fun main(args: Array<String>) when it does.
References
- Kotlin command-line compiler (create and run an application) (JetBrains (kotlinlang.org))
- Kotlin compiler options (-d, -include-runtime and -no-reflect) (JetBrains (kotlinlang.org))
- Basic syntax: program entry point (JetBrains (kotlinlang.org))
- Get started with Kotlin/JVM (JetBrains (kotlinlang.org))
- What's new in Kotlin 2.4.20 (the kotlinr runner command) (JetBrains (kotlinlang.org))
- Calling Kotlin from Java (package-level functions and @JvmName) (JetBrains (kotlinlang.org))
- The Java Tutorials: setting an application's entry point (Main-Class) (Oracle)
Report a problem with this lesson
Kept only in this browser. Your Learn progress