Emergency server help: get in touch

Kotlin Cheat Sheet: Syntax, Null Safety and Collections

A compact Kotlin 2.4 reference covering syntax, null safety, collections, classes, coroutines and file handling, with every example compiled by kotlinc 2.4.20.

Published 6 min read

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.

Running Kotlin

CommandWhat it does
kotlinc -versionCompiler version (2.4.20 is the current release)
kotlinc main.kt -include-runtime -d app.jarCompile to a self-contained JAR
java -jar app.jarRun it on any JVM
kotlin errs.main.kts arg1Run 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

SyntaxWhat it does
val host = "web01"Read-only (prefer this)
var port = 443Mutable; the type (Int) is inferred and fixed
val big = 10_000_000_000L · val load: Double = 0.75Long literal · explicit type
7 / 2 · 7.0 / 23 (integer division) · 3.5
"42".toInt() · "x".toIntOrNull()Parse (throws) · parse or null
var maybe: String? = nullOnly types ending in ? can hold null
maybe?.lengthSafe call: null instead of an exception
maybe?.length ?: 0Elvis operator: fallback when the left side is null
maybe?.let { println(it) }Run a block only when not null
maybe!!.lengthAssert not null; throws NullPointerException if it is
x as? IntSafe cast: null if the type does not match
if (maybe != null) maybe.lengthSmart cast: after the check, maybe is treated as String

Strings

SyntaxWhat 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

SyntaxWhat 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 bSet operations
0 until 3 · 0..<3 · 1..3 · 10 downTo 0 step 5Ranges: 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

SyntaxWhat it does
fun connect(host: String, port: Int = 22): StringDefault arguments
connect("db01", timeout = 1000)Named arguments, in any order
fun add(a: Int, b: Int) = a + bSingle-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::lengthFunction reference
fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T): TGeneric function with a bound
Scope functionObject insideReturnsTypical use
x.let { ... }itLambda resultNull checks with ?.let, transforms
x.apply { ... }thisxConfigure an object after creation
x.also { ... }itxSide effects such as logging
x.run { ... } · with(x) { ... }thisLambda resultCompute 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

SyntaxWhat 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)?.destructuredPull 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

Kotlin Cheat Sheet – short answer
In short: A compact Kotlin 2.4 reference covering syntax, null safety, collections, classes, coroutines and file handling, with every example compiled by kotlinc 2.4.20.
Kotlin Cheat Sheet – what this guide covers
Kotlin Cheat Sheet: what this guide covers.

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.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.