Short answer: Kotlin code declares read-only values with val and variables with var, and allows null only in types marked with ?, handled by ?. and ?:. Collections come with filter, map and groupBy built in, when replaces switch, and data and sealed classes cut boilerplate. The current release is Kotlin 2.4.20. The tables below show the syntax you will use most, compiled and run on the JVM.
The examples in this Kotlin cheat sheet were compiled with kotlinc 2.4.20 and run on OpenJDK 25.0.4.1 on 9 October 2026. The coroutine examples used kotlinx.coroutines 1.11.0.
Table of Contents
Running Kotlin
| Command | What it does |
|---|---|
kotlinc -version | Compiler version (2.4.20 is the current release) |
kotlinc main.kt -include-runtime -d app.jar | Compile to a self-contained JAR |
java -jar app.jar | Run it on any JVM |
kotlin errs.main.kts arg1 | Run a script directly; args holds the arguments |
kotlin("jvm") version "2.4.20" | Gradle plugin line in build.gradle.kts for a real project |
A program starts at a top-level fun main() { println("Hi") }, with no class needed. Add args: Array<String> to read command-line arguments.
Variables, types and null safety
| Syntax | What it does |
|---|---|
val host = "web01" | Read-only (prefer this) |
var port = 443 | Mutable; the type (Int) is inferred and fixed |
val big = 10_000_000_000L · val load: Double = 0.75 | Long literal · explicit type |
7 / 2 · 7.0 / 2 | 3 (integer division) · 3.5 |
"42".toInt() · "x".toIntOrNull() | Parse (throws) · parse or null |
var maybe: String? = null | Only types ending in ? can hold null |
maybe?.length | Safe call: null instead of an exception |
maybe?.length ?: 0 | Elvis operator: fallback when the left side is null |
maybe?.let { println(it) } | Run a block only when not null |
maybe!!.length | Assert not null; throws NullPointerException if it is |
x as? Int | Safe cast: null if the type does not match |
if (maybe != null) maybe.length | Smart cast: after the check, maybe is treated as String |
Strings
| Syntax | What it does |
|---|---|
"$host:$port" · "${host.uppercase()}" | String templates: a name, or any expression in braces |
"%.1f%%".format(load * 100) | printf-style formatting |
s.trim() · s.split(",") · list.joinToString("-") | Trim · split to a list · join |
s.substringBefore('.') · s.substringAfterLast('.') | web01 from web01.example.com · file extension |
s.take(2) · s.takeLast(2) · s.padStart(3, '0') | First or last characters · pad |
"error" in s · s.equals(t, ignoreCase = true) | Contains · case-insensitive compare |
s.replace(Regex("\\d"), "#") | Regex replace |
buildString { append("a"); append(1) } | Build a string efficiently |
val json = """
{
"host": "$host",
"port": $port
}
""".trimIndent() // raw string with templates; indentation removed
val tpl = $$"""{"price": "$10", "host": "$$host"}""" // multi-dollar: only $$ interpolates
Collections
| Syntax | What it does |
|---|---|
listOf("a", "b") · mapOf("ssh" to 22) · setOf(1, 2) | Read-only collections |
mutableListOf() · mutableMapOf<String, Int>() | Mutable versions |
list += "web03" · list.remove(x) | Add and remove on a mutable list |
list[0] · list.first() · list.last() · list.getOrNull(9) | Read by index; getOrNull avoids an exception |
map["mysql"] = 3306 · map.getOrPut("redis") { 6379 } | Write · read or insert |
for ((k, v) in map) | Iterate with destructuring |
nums.filter { it > 2 }.map { it * 10 } | Filter and transform; it is the single parameter |
nums.sum() · nums.maxOrNull() · nums.count { it % 2 == 0 } | Aggregates |
nums.sorted() · hosts.sortedBy { it.length } | Sorted copies |
hosts.groupBy { it.take(2) } | Map of key to list |
hosts.groupingBy { it.take(2) }.eachCount() | Count per key |
hosts.associateWith { it.length } | Map from each item to a value |
nums.partition { it > 2 } · nums.chunked(2) | Split in two · batches |
a intersect b · a union b · a subtract b | Set operations |
0 until 3 · 0..<3 · 1..3 · 10 downTo 0 step 5 | Ranges: exclusive, exclusive, inclusive, reverse |
Control flow and when
val status = if (code < 500) "up" else "down" // if is an expression
fun classify(code: Int): String = when (code) {
in 200..299 -> "ok"
301, 302 -> "redirect"
in 400..499 -> "client error"
else -> "server error"
}
fun describe(x: Any?): String = when (x) {
null -> "null"
is Int if x > 1000 -> "big int" // guard condition
is Int -> "int $x" // smart cast to Int
is String -> "string of ${x.length}"
else -> "other"
}
Loops use for (i in 0 until 3), while and do ... while. Labels such as outer@ with break@outer or continue@outer control nested loops. A when over a sealed type or enum needs no else, because the compiler checks that every case is covered.
Functions, lambdas and scope functions
| Syntax | What it does |
|---|---|
fun connect(host: String, port: Int = 22): String | Default arguments |
connect("db01", timeout = 1000) | Named arguments, in any order |
fun add(a: Int, b: Int) = a + b | Single-expression function |
fun sum(vararg n: Int) = n.sum() | Variable number of arguments |
fun String.isPrivateIp(): Boolean = startsWith("10.") | Extension function on an existing type |
val square: (Int) -> Int = { it * it } | Lambda with a function type |
retry(3) { ping() } | Trailing lambda: the last lambda argument goes outside the brackets |
String::length | Function reference |
fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T): T | Generic function with a bound |
| Scope function | Object inside | Returns | Typical use |
|---|---|---|---|
x.let { ... } | it | Lambda result | Null checks with ?.let, transforms |
x.apply { ... } | this | x | Configure an object after creation |
x.also { ... } | it | x | Side effects such as logging |
x.run { ... } · with(x) { ... } | this | Lambda result | Compute a value from an object |
Classes, data classes and sealed types
data class Server(val name: String, val ip: String, val port: Int = 22) {
init { require(port in 1..65535) { "bad port: $port" } }
val address get() = "$ip:$port"
}
val srv = Server("web01", "10.0.0.5", 443)
val copy = srv.copy(port = 8443) // equals, hashCode, toString, copy are generated
val (name, ip) = srv // destructuring
sealed interface Result {
data class Ok(val value: Int) : Result
data class Err(val message: String) : Result
data object Pending : Result
}
class Db(val host: String, private val port: Int = 3306) {
companion object { fun local() = Db("localhost") } // like static members
var status = "down"
private set // public read, private write
}
enum class Level(val code: Int) { INFO(1), WARN(2), ERROR(3) }
object Registry { val items = mutableListOf<String>() } // singleton
@JvmInline value class Port(val value: Int) // type-safe wrapper, no allocation
Classes and members are final by default; mark them open to allow inheritance and use override in the subclass. Visibility is public unless you say private, protected or internal (same module).
Exceptions and coroutines
fun parsePort(v: String): Int = v.toIntOrNull() ?: throw ConfigException("bad port: $v")
val p = try { parsePort("80") } catch (e: ConfigException) { -1 } // try is an expression
val res = runCatching { parsePort("x") }
res.getOrDefault(22) // 22
res.exceptionOrNull()?.message // "bad port: x"
require(port > 0) { "port must be positive" } // IllegalArgumentException
check(connected) { "not connected" } // IllegalStateException
error("boom") // IllegalStateException
Kotlin has no checked exceptions, so nothing forces you to catch. Coroutines need the org.jetbrains.kotlinx:kotlinx-coroutines-core library:
suspend fun check(host: String): String { delay(10); return "$host ok" }
fun main() = runBlocking {
val results = listOf("web01", "web02").map { h -> async { check(h) } }.awaitAll()
println(results) // [web01 ok, web02 ok]
val r = withTimeoutOrNull(2_000) { check("db01") } // null if it takes too long
}
For server admins: files, processes and scripts
| Syntax | What it does |
|---|---|
File("app.log").readText() · .readLines() | Whole file · list of lines (small files) |
file.useLines { lines -> lines.count { "ERROR" in it } } | Stream a large file and close it automatically |
file.forEachLine { ... } | Process line by line |
file.writeText(s) · file.appendText(s) | Overwrite · append |
File("demo").mkdirs() · file.exists() · file.length() | Create directories · test · size |
dir.walk().filter { it.extension == "log" } | Recursive search |
ProcessBuilder("df", "-h", "/").redirectErrorStream(true).start() | Run a command without a shell, then waitFor() |
Regex("""^(\S+) .*" (\d{3}) """).find(line)?.destructured | Pull the IP and status from an access-log line |
For small admin tasks, a .main.kts script runs with kotlin errs.main.kts without a build tool. It can import any JDK class, so java.io.File, ProcessBuilder and java.net.http.HttpClient are all available.
Official documentation (checked 9 October 2026): Kotlin: Basic syntax overview · Kotlin: Null safety · Kotlin: Collections overview · Kotlin: Coroutines basics · What’s new in Kotlin 2.4.20
Kotlin Cheat Sheet at a glance


Related guides: Java Cheat Sheet: Syntax, Collections, Records and Streams · Swift Cheat Sheet: Syntax, Optionals, Protocols and async · Dart 3 Cheat Sheet: Null Safety, Records, Patterns and async.
Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.