Kotlin #
There are moments when a programming language emerges not to replace what already exists, but to solve a real problem that has been ignored for far too long. Kotlin is that moment for the JVM ecosystem. JetBrains — the company that builds the best IDEs in the industry — was frustrated writing millions of lines of Java every day: too verbose, far too easy to produce NullPointerException, too much boilerplate for things that should be simple. So they built their own language. Kotlin wasn’t designed as an academic experiment, but as a production tool that had to replace Java line by line — still running on the same JVM, still calling the same Java libraries, but with far more expressive syntax and a much safer type system. The result is a language that has been the official language of Android since 2017, and has since spread far beyond that.
What Is Kotlin? #
Kotlin is a statically-typed programming language developed by JetBrains and released as open source in 2012. Kotlin runs on top of the Java Virtual Machine (JVM), can be compiled to JavaScript for web development, and through Kotlin/Native can be compiled to native code with no JVM at all — including for iOS and embedded systems.
What makes Kotlin special isn’t any single feature, but how it combines pragmatism and elegance: full interoperability with Java so adoption can be gradual, built-in null safety that eliminates an entire class of bugs, concise syntax that dramatically cuts boilerplate, and coroutines that make asynchronous programming feel natural.
// The Java equivalent needs ~20 lines with getter, setter, equals, hashCode, toString
// Kotlin: one line
data class User(val id: Int, val name: String, val email: String)
fun main() {
val user = User(1, "Budi", "[email protected]")
println(user) // User(id=1, name=Budi, [email protected])
// Null safety: the compiler prevents NPEs
val name: String? = null // ? = nullable
println(name?.uppercase()) // null — no crash
println(name ?: "No Name") // "No Name" — elvis operator
}
flowchart LR
A["Kotlin Code\n(.kt)"] -->|kotlinc| B["JVM Bytecode\n(.class / .jar)"]
A -->|kotlin-js| C["JavaScript\n(.js)"]
A -->|Kotlin/Native| D["Native Binary\n(iOS, Linux, Windows, macOS)"]
B -->|runs on| E["JVM\n(server, Android, desktop)"]
C -->|runs on| F["Browser / Node.js"]
D -->|runs on| G["No JVM\n(iOS, embedded)"]Why Kotlin Exists — The Problems It Solves #
JetBrains didn’t create Kotlin because they wanted to invent a new language. They created it because they themselves felt the pain of writing Java at scale, every single day.
NullPointerException — The Billion Dollar Mistake #
Tony Hoare, the inventor of the null reference, called it his “billion dollar mistake” — an estimate of the damage NPEs have caused across the industry. Java doesn’t distinguish between references that can be null and those that can’t at the type system level. Every object is potentially null, and you only find out at runtime.
// ANTI-PATTERN (Java style, NPE-prone):
fun getName(user: User?): String {
return user.name.uppercase() // Compilation error: user can be null!
}
// CORRECT (Kotlin null safety):
fun getName(user: User?): String {
return user?.name?.uppercase() ?: "Anonymous"
// ?. = safe call: null if user is null
// ?: = elvis: default value if null
}
// Smart cast: after the null check, the compiler knows the type
fun process(value: String?) {
if (value != null) {
println(value.length) // String, not String? — no ?. needed
}
}
Kotlin separates nullable (String?) and non-nullable (String) types at the type system level. The compiler refuses to compile code that could produce an NPE without an explicit check. An entire class of bugs — ones that in Java only surface at runtime in production — simply disappears in Kotlin.
The Never-Ending Boilerplate #
Java requires a lot of code for things that should be trivial. Kotlin eliminates almost all of that boilerplate:
// ANTI-PATTERN (Java-style in Kotlin):
class Product {
private var name: String = ""
private var price: Double = 0.0
constructor(name: String, price: Double) {
this.name = name
this.price = price
}
fun getName() = name
fun getPrice() = price
// ... equals, hashCode, toString must be written manually
}
// CORRECT (idiomatic Kotlin):
data class Product(val name: String, val price: Double)
// equals, hashCode, toString, copy — all generated automatically
| Feature | Java | Kotlin |
|---|---|---|
| Data class (POJO) | ~30 lines + Lombok | 1 line data class |
| String interpolation | "Hello, " + name + "!" | "Hello, $name!" |
| Lambda | (x) -> x * 2 | { x -> x * 2 } or { it * 2 } |
| Singleton | manual pattern | object MySingleton { } |
| Default parameter values | method overloading | fun f(x: Int = 0) |
| Extension function | static utils class | fun String.shout() = uppercase() |
History and Evolution #
flowchart TD
A["2010\nJetBrains starts the\ninternal Kotlin project\nFrustrated with Java\nat scale"] --> B["2011\nKotlin announced publicly\nNamed after Kotlin Island\nnear St. Petersburg"]
B --> C["2012\nOpen source\nunder Apache 2.0\nPre-release versions available"]
C --> D["2016\nKotlin 1.0 released\nStable for production\nJVM & JavaScript support"]
D --> E["2017\nGoogle I/O\nKotlin becomes the\nofficial Android language\nAdoption explodes"]
E --> F["2018\nKotlin/Native\nCompiles to iOS\nand non-JVM platforms"]
F --> G["2019\nGoogle: Kotlin-first\nAndroid SDK & docs\nprioritized for Kotlin"]
G --> H["2021\nKotlin Multiplatform Mobile\nbeta — iOS + Android\nfrom one codebase"]
H --> I["2023\nKotlin Multiplatform Stable\nKMP officially stable\nfor production"]
I --> J["2024–present\nKotlin 2.0\nNew K2 compiler\n2x faster compilation"]The biggest turning point was Google I/O 2017, when Google announced Kotlin as an official Android language — on par with Java. Two years later at Google I/O 2019, Google declared its Kotlin-first approach: Android documentation, APIs, and code samples began to be prioritized in Kotlin. This wasn’t just support — it was an industry signal that Java on Android was de facto sunset.
Kotlin 2.0 (released 2024) is the next major leap — not on the language feature side, but on the compiler side. The K2 compiler, rewritten from scratch, delivers up to 2x faster compilation than the previous compiler. For large codebases, this means a much shorter feedback loop during development.
Kotlin’s Standout Features #
Null Safety #
Already covered above, but in more detail: Kotlin has a whole set of operators for working with nullable values elegantly.
data class Address(val city: String, val zipCode: String?)
data class User(val name: String, val address: Address?)
val user: User? = getUser()
// Safe call chaining: stops at any null, returns null
val zipCode: String? = user?.address?.zipCode
// Elvis operator: default value if null
val city = user?.address?.city ?: "Unknown city"
// let: block that only executes if not null
user?.let { u ->
println("Hello, ${u.name}")
sendNotification(u)
}
// Non-null assertion: use only if you're SURE it's not null
// (will throw an NPE if null — use with caution)
val fullName = user!!.name
Data Classes #
data class is Kotlin’s way of defining classes whose job is to hold data. The compiler automatically generates equals(), hashCode(), toString(), and copy().
data class Order(
val id: Int,
val product: String,
val quantity: Int,
val price: Double
) {
val total: Double get() = quantity * price
}
val order = Order(1, "Laptop", 2, 15_000_000.0)
println(order) // Order(id=1, product=Laptop, quantity=2, price=1.5E7)
// copy: create a new object with some fields changed
val revisedOrder = order.copy(quantity = 3)
println(revisedOrder.total) // 45_000_000.0
// Destructuring
val (id, product, quantity, price) = order
println("$product x $quantity = Rp ${quantity * price}")
Extension Functions #
Extension functions let you add methods to existing classes — including classes from libraries you can’t modify — without inheritance.
// Add a method to String without subclassing
fun String.titleCase(): String =
split(" ").joinToString(" ") { word ->
word.replaceFirstChar { it.uppercase() }
}
fun Double.formatRupiah(): String =
"Rp ${\"%,.0f\".format(this).replace(\",\", \".\")}"
fun main() {
println("halo dunia".titleCase()) // Halo Dunia
println(15_000_000.0.formatRupiah()) // Rp 15.000.000
// Extension functions on your own types
println(listOf(3, 1, 4, 1, 5).sum()) // 14 (already in the stdlib)
}
Coroutines — Clean Asynchronous Programming #
Coroutines are Kotlin’s answer to asynchronous programming. Unlike callback hell or the complexity of reactive streams, coroutines let you write asynchronous code that looks sequential but doesn’t block threads.
import kotlinx.coroutines.*
// ANTI-PATTERN: callback hell (the old way)
fun getDataCallback(
onSuccess: (User) -> Unit,
onError: (Exception) -> Unit
) {
fetchUserFromApi { user, error ->
if (error != null) { onError(error); return@fetchUserFromApi }
fetchOrdersFromApi(user.id) { orders, error2 ->
if (error2 != null) { onError(error2); return@fetchOrdersFromApi }
// the deeper you go, the more unreadable it gets...
}
}
}
// CORRECT: coroutines — looks sequential, doesn't block threads
suspend fun getData(): Pair<User, List<Order>> {
val user = fetchUserFromApi() // suspend: doesn't block the thread
val orders = fetchOrdersFromApi(user.id) // waits for the result
return user to orders
}
fun main() = runBlocking {
val (user, orders) = getData()
println("${user.name} has ${orders.size} orders")
}
Coroutines also support structured concurrency — if one coroutine fails, the entire related group of coroutines is cleaned up automatically.
// Run several operations in parallel
suspend fun getDashboard(): Dashboard = coroutineScope {
val userDeferred = async { fetchUserFromApi() }
val topProductsDeferred = async { fetchTopProducts() }
val statsDeferred = async { computeStats() }
// All three run in parallel, wait for all to finish
Dashboard(
user = userDeferred.await(),
topProducts = topProductsDeferred.await(),
stats = statsDeferred.await()
)
}
Sealed Classes and When Expressions #
sealed class is Kotlin’s way of defining a closed class hierarchy — all subclasses must be defined in the same file. This makes when expressions exhaustive: the compiler ensures every case is handled.
sealed class OperationResult<out T> {
data class Success<T>(val data: T) : OperationResult<T>()
data class Failure(val message: String, val code: Int) : OperationResult<Nothing>()
object Loading : OperationResult<Nothing>()
}
fun showResult(result: OperationResult<User>) {
when (result) {
is OperationResult.Success -> println("Hello, ${result.data.name}")
is OperationResult.Failure -> println("Error ${result.code}: ${result.message}")
OperationResult.Loading -> println("Loading...")
// No else needed — the compiler knows all cases are handled
}
}
Where Is Kotlin Used? #
Android — The Main Habitat #
Kotlin has been the primary Android language since Google declared its Kotlin-first approach in 2019. All modern Android documentation, Jetpack libraries, and Jetpack Compose are written and optimized for Kotlin.
// Jetpack Compose: modern Android UI with Kotlin
@Composable
fun ProductCard(product: Product, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick() }
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = product.name, style = MaterialTheme.typography.titleMedium)
Text(text = product.price.formatRupiah(), color = MaterialTheme.colorScheme.primary)
}
}
}
Backend Servers #
Kotlin works seamlessly with the entire Java ecosystem — including Spring Boot, the most popular backend framework in the enterprise world. On the Kotlin-native side, Ktor (from JetBrains itself) is an asynchronous framework built on top of coroutines.
// Ktor: Kotlin-native backend framework
fun Application.configureRouting() {
routing {
get("/products/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid ID")
val product = productService.findById(id)
?: return@get call.respond(HttpStatusCode.NotFound, "Product not found")
call.respond(product)
}
}
}
Kotlin Multiplatform (KMP) #
This is Kotlin’s most exciting frontier right now. KMP lets a single codebase share business logic between Android, iOS, web, and desktop — while the UI stays native to each platform.
flowchart TD
Shared["Shared Kotlin Code\n(Business Logic, Data, Network, Storage)"] --> Android["Android\nJetpack Compose UI\n.kt → JVM bytecode"]
Shared --> iOS["iOS\nSwiftUI / UIKit\n.kt → Native iOS binary"]
Shared --> Web["Web (JS)\nReact / Compose Web\n.kt → JavaScript"]
Shared --> Desktop["Desktop\nCompose for Desktop\n.kt → JVM"]
Shared --> Server["Backend / Server\nKtor, Spring Boot\n.kt → JVM bytecode"]// expect/actual: platform-specific implementation
// In shared code:
expect fun platformName(): String
// On Android:
actual fun platformName(): String = "Android ${Build.VERSION.SDK_INT}"
// On iOS:
actual fun platformName(): String = UIDevice.currentDevice.systemName()
| Domain | Tool / Framework |
|---|---|
| Android UI | Jetpack Compose |
| iOS UI (with KMP) | SwiftUI + shared Kotlin logic |
| Backend | Ktor, Spring Boot, Quarkus, Vert.x |
| Desktop | Compose for Desktop |
| Web Frontend | Kotlin/JS, Compose Web |
| Shared Logic | Kotlin Multiplatform (KMP) |
| Build Tool | Gradle (Kotlin DSL) |
Kotlin vs Java — A Practical Comparison #
This isn’t about who is “better” in absolute terms — it’s about context and design decisions.
// Comparing implementations of the same thing
// --- Java ---
// public class User {
// private final String name;
// private final String email;
// private final Integer age;
//
// public User(String name, String email, Integer age) { ... }
// public String getName() { return name; }
// public String getEmail() { return email; }
// public Integer getAge() { return age; }
// @Override public boolean equals(Object o) { ... }
// @Override public int hashCode() { ... }
// @Override public String toString() { ... }
// }
// --- Kotlin (exactly equivalent) ---
data class User(val name: String, val email: String, val age: Int?)
flowchart TD
Q{"Project context?"} --> A["Existing large Java codebase"]
Q --> B["New Android project"]
Q --> C["New JVM backend"]
Q --> D["Need iOS + Android from one codebase"]
A --> E["Kotlin — full interop,\ngradual migration"]
B --> F["Kotlin — Kotlin-first Android,\nJetpack Compose"]
C --> G["Kotlin — Ktor or Spring Boot,\nnative coroutines"]
D --> H["Kotlin Multiplatform\none codebase, native UI"]Kotlin is fully interoperable with Java — you can call Java code from Kotlin and vice versa within the same project. This means there’s no need to rewrite an existing Java codebase all at once. Migration can happen file by file, module by module, at your team’s own pace.
What You’ll Learn in This Documentation #
This documentation builds your understanding of Kotlin from the foundations to real-world usage, with an emphasis on proper Kotlin idioms — not just Java written with Kotlin syntax.
flowchart TD
A["Basics\nInstallation & Build Tools\nval/var Variables, Data Types\nControl Flow, Functions\nClasses, Interfaces, Exceptions\nCollections (List, Map)"] --> B["Advanced\nCoroutines & Async\nI/O & Socket\nWebSocket & Web Server\nUnit Test & Mocking"]
B --> C["Other Topics\nSQL & NoSQL Databases\nMessage Broker & Cache\nFrameworks (Ktor, Quarkus, Vert.x)\nLibraries & Articles"]
C --> D["Standard Library\nStrings, IO, Math"]The Basics section builds a genuinely Kotlin foundation: the difference between val and var, a type system with null safety, functions with default parameters and named arguments, classes and data classes, interfaces, sealed classes, exception handling, and Kotlin’s rich collection framework (immutable vs mutable).
The Advanced section dives into Kotlin’s real strengths: coroutines for asynchronous programming, I/O operations, sockets and WebSockets, building a web server, and testing with JUnit 5 and MockK — a mocking library designed specifically for Kotlin.
The Other Topics section is Kotlin in the real world: integration with relational databases (MySQL, PostgreSQL, Oracle, MSSQL) and NoSQL (MongoDB, Elasticsearch), message brokers (Kafka, RabbitMQ, SQS, Pub/Sub), caching (Redis, Memcached), and Kotlin frameworks such as Ktor, Quarkus, and Vert.x.
The Standard Library section covers Kotlin’s utility modules: rich string manipulation, I/O operations, and math functions — all with APIs that are more Kotlin-idiomatic than their Java counterparts.
Summary #
- Kotlin runs on the JVM and is fully interoperable with Java — you can call Java libraries from Kotlin and vice versa without extra configuration. Migration from Java can be gradual.
- Built-in null safety eliminates the entire NPE class of bugs — the Kotlin compiler distinguishes nullable (
String?) and non-nullable (String) types and refuses to compile code that could crash on null without an explicit check.data classreplaces Java POJOs/boilerplate —equals,hashCode,toString, andcopyare generated automatically. One line replaces dozens of Java lines.- Coroutines are Kotlin’s native way of doing async — write asynchronous code that looks sequential, without callback hell, without the complexity of reactive streams. Coroutines are lightweight: thousands can run on a single thread.
- Extension functions extend classes without inheritance — add methods to
String,List, or any class from a library you can’t modify.- Kotlin 2.0 brings the K2 compiler — compilation up to 2x faster than before, with no changes to existing code.
- Kotlin Multiplatform (KMP) enables one codebase for Android, iOS, web, and backend — share business logic, write native UI per platform.
whenwithsealed classis exhaustive — the compiler ensures all cases are handled, no case silently slips through.- Kotlin-first on Android — since 2019, all Android documentation, Jetpack libraries, and tooling prioritize Kotlin. Java on Android is a legacy, not the future.
Next: Installation →