Why Kotlin Exists — Frustration, an Island, and a Better JVM
How a team tired of writing Java built a language on an island near Saint Petersburg, won over Google and Android, and made concurrency a joy with coroutines. The origin story behind the language.
The problem with Java
Most languages are born from frustration. Kotlin is no exception.
In 2010, JetBrains had a problem that few companies share: they wrote tools for programmers for a living, and they wrote them in Java. IntelliJ IDEA — their flagship product — was a sprawling codebase of millions of lines of Java. Every day, their own engineers felt Java's friction firsthand. The endless getters and setters. The ceremony of an anonymous inner class just to pass a single callback. And, lurking everywhere, the dreaded NullPointerException.
Java was reliable and fast, and the JVM (Java Virtual Machine) it ran on was one of the most battle-tested pieces of software on Earth. But the language itself had calcified. Between Java 6 (2006) and Java 7 (2011), the language barely moved. Sun Microsystems — Java's creator — was being acquired by Oracle, and the language's evolution had slowed to a crawl. Programmers wanted more, and Java wasn't giving it to them.
JetBrains looked around at the alternatives. The most obvious candidate was Scala — a powerful, expressive language that also ran on the JVM and interoperated with Java. Scala had everything Java lacked: type inference, functional programming, concise syntax. The JetBrains team actually liked Scala. But there was a catch that, for a tools company, was fatal.
Scala compiled too slowly. If your entire business is building a fast, responsive IDE, you cannot have your developers waiting on a sluggish compiler all day. And you certainly cannot ask your customers to wait on it. So JetBrains made a decision that sounds slightly mad in hindsight: if no existing language fit, they would build their own.
The goals were pragmatic, not academic. They didn't want to invent a beautiful new paradigm. They wanted a language that was: concise (less boilerplate than Java), safe (no more random null crashes), interoperable (call Java seamlessly, because rewriting IntelliJ from scratch was out of the question), and — critically — fast to compile.
An island named Kotlin
Every language needs a name. Java was named after the Indonesian island famous for its coffee — which is why its logo is a steaming cup and why so much Java code lives in packages full of "beans". JetBrains decided to honour that tradition, with a twist closer to home.
They named the language Kotlin, after Kotlin Island — a small island in the Gulf of Finland, just off the coast of Saint Petersburg, Russia, where JetBrains' development team was based. The logic was charming: "Java is named after an island, so we'll name ours after an island too." The island is home to the town of Kronstadt and has guarded the sea approach to Saint Petersburg since the days of Peter the Great.
The language was first announced publicly in July 2011 by Andrey Breslav, Kotlin's lead language designer, at a JVM Language Summit. The reaction was lukewarm — the world had plenty of "better Java" projects, and most went nowhere. In February 2012, JetBrains open-sourced Kotlin under the Apache 2 licence, a deliberate signal that this was not a proprietary toy but a language meant to be trusted and adopted widely.
Then came years of patient, unglamorous work. Version 1.0 — the first release JetBrains promised to support long-term — did not arrive until February 2016. Five years from announcement to 1.0. That slow burn is itself a piece of Kotlin's character: it was designed by people who maintained enormous codebases and knew that stability matters more than novelty.
Andrey Breslav led the language design until 2020, when Roman Elizarov — the engineer most associated with Kotlin's coroutines — took over as project lead. The torch passing from a language designer to a concurrency expert is itself a hint about where Kotlin's centre of gravity had moved.
Pragmatic by design
Kotlin's designers have a favourite word: pragmatic. They were not trying to win academic arguments. They were trying to make working programmers faster and their programs safer. Every feature was weighed against a simple test: does this help people who ship real software?
The clearest demonstration is the humble data class. In Java, modelling a simple "user with a name and age" means writing a constructor, two getters, equals(), hashCode(), and toString() — dozens of lines of mechanical, error-prone boilerplate. In Kotlin, it is one line.
The data keyword tells the compiler to generate equals(), hashCode(), toString(), and a copy() method automatically. Notice also val — Kotlin's keyword for an immutable, read-only reference (its mutable sibling is var). Immutability is the default you reach for; mutability is opt-in. That single choice quietly eliminates a whole category of bugs.
Conciseness is not the only pillar. Kotlin's design rests on a few deliberate decisions:
• Interoperability first. Kotlin can call any Java library and Java can call Kotlin. This was non-negotiable — it meant teams could adopt Kotlin one file at a time, never rewriting everything.
• Tooling as a feature. JetBrains builds IDEs; Kotlin was designed alongside its tooling, so autocomplete, refactoring, and Java-to-Kotlin conversion were first-class from day one.
• No surprises. Kotlin avoids "clever" features that make code hard to read. It is expressive, but it deliberately stops short of Scala's full power in exchange for being easier to learn and faster to compile.
The null story, revisited
If you have read the Dart series, you already know the villain of this section. In 1965, the British computer scientist Tony Hoare invented the null reference for the ALGOL W language. Decades later he publicly apologised for it, calling null his "billion-dollar mistake" — because null reference errors have caused more crashes than perhaps any other single feature in the history of programming.
Java inherited null wholesale, and with it the infamous NullPointerException. Kotlin's answer is the same elegant idea Dart later adopted: make nullability part of the type. By default, a Kotlin type cannot hold null. To allow it, you add a ?.
var name: String = "Ankit"
name = null // compile error — String cannot be null
var nickname: String? = "Ank"
nickname = null // fine — String? allows null
Once a type is nullable, the compiler refuses to let you use it carelessly. To work with nullable values, Kotlin gives you a small, memorable toolkit of operators — and the names behind them are a fun slice of programming folklore.
The Elvis operator
?: earned its name because, tilted on its side, the ? and : resemble Elvis Presley's iconic quiff and eyes. It supplies a fallback: val len = name?.length ?: 0 reads as "the length, or zero if name is null." The double-bang !! is sometimes called the "shouting" operator — it loudly insists a value is not null, and if you are wrong, it throws the very NullPointerException you were trying to avoid. Seasoned Kotlin developers treat !! as a code smell.
Kotlin also has a trick Dart lacks the equivalent pressure for: smart casts. Once you check that a value is not null, the compiler "promotes" it and lets you use it as non-null without ceremony.
fun greet(name: String?) {
if (name != null) {
// smart cast: name is now treated as String, not String?
println(name.uppercase())
}
}
The interop gotcha. Here is where Kotlin's null safety meets the messy real world. When Kotlin calls a Java method, the compiler often cannot know whether Java will return null — Java's types carry no such information. Kotlin calls these platform types, written internally as String! (with a single bang). They are an escape hatch: Kotlin trusts you to handle them correctly. It is the one place where Kotlin's otherwise airtight null safety has a deliberate leak — the price of seamless Java interoperability.
Google, Android, and the 2017 moment
For its first five years, Kotlin was a respected niche language with a devoted following. Then, in May 2017, everything changed in a single announcement.
At Google I/O 2017, Google announced first-class support for Kotlin on Android. For the millions of developers building Android apps — who had been stuck writing Java, and an old dialect of Java at that — this was electrifying. Android's Java was frozen at a roughly Java 7/8 level for compatibility reasons, meaning Android developers were missing years of language improvements. Kotlin instantly handed them everything they had been craving: null safety, concise syntax, lambdas, extension functions.
There was also a strategic subtext. Around the same time, Oracle and Google were locked in a long, bitter lawsuit over Google's use of the Java APIs in Android — a case that would eventually reach the U.S. Supreme Court. Kotlin, with its open-source Apache licence and independent steward in JetBrains, offered Google a path that did not depend so heavily on Oracle's Java. Whatever the mix of technical love and legal caution, the effect was the same: Kotlin's adoption exploded.
Two years later, at Google I/O 2019, Google went further and declared Android development "Kotlin-first" — meaning new APIs, documentation, and samples would prioritise Kotlin over Java. For a language that started as one company's internal frustration, becoming the preferred language of the world's most popular operating system was a staggering arc.
The lesson is worth pausing on: Kotlin's interoperability — that non-negotiable design pillar from the start — is exactly what made this possible. Because Kotlin and Java run on the same JVM and call each other freely, an Android team could adopt Kotlin one screen, one file, even one line at a time. No big rewrite, no risky migration. That gradual on-ramp, more than any single feature, is why Kotlin won.
Compiles to everything
Kotlin began as a JVM language, but it did not stay confined to the JVM. Today the same Kotlin source can be compiled to run almost anywhere — a flexibility that powers Kotlin Multiplatform, where shared business logic is written once and run on many platforms.
Kotlin/JVM is the original: your code compiles to Java bytecode and runs on any JVM — Android, backend servers, desktop apps. Kotlin/Native uses the LLVM compiler toolchain to produce standalone native binaries with no JVM at all, which is how Kotlin reaches iOS and embedded devices. Kotlin/JS transpiles to JavaScript for the browser, and the newer Kotlin/Wasm targets WebAssembly for near-native speed on the web.
This is the same "write once" promise that Flutter and Dart make, approached from a different angle. Where Flutter ships its own rendering engine, Kotlin Multiplatform typically shares only the logic layer and lets each platform keep its native UI. Two philosophies, both chasing the same dream of not writing the same code three times.
Kotlin's superpower: coroutines
If null safety is the feature that first attracts people to Kotlin, coroutines are the feature that makes them stay. Coroutines are Kotlin's answer to one of the hardest problems in programming: how to write code that waits — for a network request, a database, a file — without freezing your program or drowning in callbacks.
The idea is old. It traces back to Communicating Sequential Processes (CSP), a model described by Tony Hoare (yes, the same Hoare) in 1978. It echoes the async/await that C# popularised, and the lightweight "goroutines" of the Go language. Kotlin's team — led on this front by Roman Elizarov — synthesised these ideas into something that felt native to the language. Coroutines arrived experimentally in Kotlin 1.1 (2017) and became stable in Kotlin 1.3, in October 2018.
The core insight is the suspend keyword. A suspending function can pause partway through and resume later, without blocking the thread it was running on. While it waits, the thread is free to do other work.
suspend fun loadUser(): User {
val profile = fetchProfile() // suspends here, thread is free
val photo = fetchPhoto() // suspends again
return User(profile, photo)
}
Why does this matter so much? Because the traditional unit of concurrency — the thread — is expensive. Each thread reserves about a megabyte of memory and is managed by the operating system. A server might handle a few thousand threads before grinding to a halt. Coroutines are different: they are cheap, managed by Kotlin itself, and you can launch millions of them on a handful of threads.
Kotlin's other great contribution here is structured concurrency. Coroutines are launched inside a
CoroutineScope, forming a parent-child tree. If the parent is cancelled — say, the user leaves the screen — all its children are cancelled automatically. If one child fails, its siblings can be cancelled too. This sounds mundane, but it eliminates a classic bug: the orphaned background task that keeps running, leaking memory and crashing on a screen that no longer exists.
// launch fires off work that returns nothing
scope.launch { saveToDatabase() }
// async returns a value you can await
val deferred = scope.async { computeResult() }
val result = deferred.await()
Two famous gotchas. First: suspend does not mean "run in parallel." A suspending function runs sequentially by default — the two fetch calls above happen one after another. To run them concurrently you must explicitly use async and await both. Second: because cancellation is cooperative, a coroutine stuck in a tight CPU loop that never suspends cannot be cancelled. You have to give it a chance to check, which is why coroutine code sprinkles in yield() or checks isActive.
Flow — cold streams done right
A single suspend function returns one value. But programs often deal with streams of values over time: location updates, search results as you type, rows arriving from a database. Kotlin's answer is Flow — a coroutine-native, asynchronous stream.
fun temperatures(): Flow<Int> = flow {
while (true) {
emit(readSensor()) // push a value
delay(1000) // wait a second (suspends)
}
}
// nothing happens until someone collects
temperatures().collect { temp -> println(temp) }
The crucial property is that a basic Flow is cold. The code inside the flow { } builder does not run when you create the flow — it runs only when something collects it, and it runs fresh for each collector. This is the opposite of a hot stream (like SharedFlow or a Channel), which produces values whether or not anyone is listening, and shares them among collectors.
It helps to place Flow among its relatives:
•
Sequence — lazy, but synchronous. It cannot suspend, so it is for in-memory computation, not waiting on I/O.•
Flow — lazy and asynchronous. It can suspend with delay or network calls. Cold by default.•
Channel — a hot pipe between coroutines, like a queue; values are consumed once.Flow comes with a rich library of operators —
map, filter, debounce, combine — that should feel familiar to anyone who has used reactive streams like RxJava, but built natively on coroutines and structured concurrency. It is the foundation of modern reactive Kotlin, and the natural subject for the next episode in this series. Coroutines gave Kotlin a way to pause; Flow gives it a way to stream.
AI prompts — exploring Kotlin
Use these prompts with an AI assistant to go deeper on the ideas in this episode.
Prompt 1 — Java to Kotlin, idiomatically:
Convert this Java class to idiomatic Kotlin:
[paste Java code]
Use data classes, val/var appropriately, null-safe types,
and explain each idiom you applied and why.
Prompt 2 — Null safety patterns:
Review this Kotlin code for null-safety smells:
[paste code]
Flag every !! operator, suggest safer alternatives using ?., ?:,
or smart casts, and point out any platform types coming from Java
that I'm not handling.
Prompt 3 — Coroutines vs threads:
Explain how this blocking, thread-based code would be rewritten
with Kotlin coroutines:
[paste code]
Show me where it suspends, whether the work runs sequentially or
concurrently, and how structured concurrency handles cancellation.
Prompt 4 — Cold vs hot streams:
I have a stream of [search results / sensor readings / UI events].
Should I model it as a cold Flow, a SharedFlow, or a Channel?
Walk through the trade-offs for my case, and show the code for
the option you recommend.
Why Kotlin Exists — Quiz
7 questions
Seven questions on Kotlin's origins, its null safety, compilation targets, and the coroutines and Flow that define it.