Many developers learn Kotlin by writing Java... just with .kt files.
The code compiles. The app runs. But something feels heavy — like you're driving with the handbrake on. That heaviness has a name: you're writing Kotlin with a Java accent.
Every Language Has an Accent
Spend time in any language community and you'll hear it:
- Python has Pythonic.
- Go has idiomatic Go.
- Ruby has the Ruby way.
- Rust has its own idioms.
Each is a shared sense of what "natural" looks like. You can write any language with another one's accent — the compiler won't stop you — but other developers hear it instantly. The code works; it just reads like a translation.
Kotlin is no exception. And because so many people arrive from Java, the most common accent in Kotlin codebases is a Java one.
So What Is "Idiomatic" Kotlin?
In everyday language, an idiom is a phrase native speakers reach for without thinking — "break a leg," "piece of cake." You could say "good luck" or "that's easy," but the idiom is what sounds natural.
Idiomatic Kotlin is the same: writing Kotlin the way experienced Kotlin developers write it, leaning on the language's strengths instead of forcing Java habits into a .kt file.
Here's what migrants miss:
You switched to Kotlin for its safety and concision. Writing it the Java way quietly opts back out of the exact gains you came for.
So idiomatic isn't about looking clever. It's about actually collecting the benefits you switched for — grouped here under the two qualities Kotlin is built to enforce.
Safety Isn't Optional
Kotlin moves the most common runtime failures into the type system, so careless code simply doesn't compile.
Prefer val — make immutability the default
Idiomatic Kotlin reaches for val (read-only) first, and uses var only when something genuinely must change. Immutable data can't be modified by accident and is safe to share across threads.
val name = "Maureen" // read-only
var attempts = 0 // mutable — only because it really changesReach for read-only collections the same way:
val users = listOf("Ada", "Linus") // can't be added to or clearedComing from Java? In Java 25 (the current LTS), mutability is the default and immutability is opt-in — you sprinkle final everywhere, and new ArrayList<>() is mutable unless you remember List.of(...). Kotlin flips the default: read-only first, mutable on purpose.
Let the type system handle null
Kotlin builds nullability into the type system. A String can never be null; a String? might be — and the compiler forces you to handle the difference. The idioms make that painless:
val length = user?.name?.length ?: 0 // safe call + default
user?.let { println(it.name) } // runs only if non-nullNo pyramids of if (x != null) checks.
Coming from Java? In Java 25, null still isn't part of the type system — any reference can be null, and the compiler can't see it. Optional helps at the edges, but it's not the same as the compiler refusing to let null slip through.
Say It Once
Kotlin generates the ceremony you'd otherwise hand-write, so the code is the meaning — nothing more. The payoff is readability: code that reads like intent.
Model data with data class
A type whose job is to hold data is one line in Kotlin. You get the constructor, equals/hashCode/toString, and copy() for free:
data class User(val name: String, val email: String)
val updated = user.copy(email = "new@example.com")Coming from Java? Java 25 has record, which closes most of this gap: record User(String name, String email) {}. Kotlin's data class still adds copy() and pairs with named and default arguments — but credit where it's due, this is one place modern Java caught up.
Treat control flow as values, not statements
In Kotlin, if, when, and try are expressions — they return a value. So you assign the result directly instead of declaring a variable and mutating it:
val label = if (score >= 50) "pass" else "fail"
val status = when (code) {
200 -> "ok"
404 -> "missing"
else -> "unknown"
}(Loops — for, while, do/while — are the exception: they stay statements.)
Coming from Java? Java 25 has switch expressions (a real improvement), but if and try are still statements — for a simple either/or you fall back to the ternary ?:, and try always needs a pre-declared variable.
Reach for when instead of long if-else chains
when is Kotlin's pattern-matching workhorse — clearer than a stack of else if, and an expression on top:
val description = when {
temp < 0 -> "freezing"
temp < 20 -> "cold"
temp < 30 -> "warm"
else -> "hot"
}Coming from Java? Java 21+ brought pattern matching to switch, so the gap is smaller than it was — but Kotlin's when is terser, needs no case/break, and works as a value out of the box.
Transform collections — don't loop
This is the most audible Java accent of all: writing an index loop to build a list. Idiomatic Kotlin describes what you want with filter, map, first, and friends:
val activeEmails = users
.filter { it.active }
.map { it.email }Coming from Java? Java has Streams that do the same — users.stream().filter(...).map(...).collect(...) — but Kotlin's collection functions are built in, with no .stream() to open or .collect() to close. Less ceremony for the everyday case.
The Rest of the Toolbox
These are the idioms you'll use in almost every file — but they're a starting point, not the whole vocabulary. Kotlin's idiom set also includes:
- scope functions —
let,apply,with,run,alsofor configuring and transforming objects - extension functions — adding behavior to types you don't own
- default & named arguments — instead of telescoping overloads
- string templates, singletons (
object), lazy properties, andusefor resource management
Rather than reprint the catalog here, I'll point you to the source that stays current: the official Kotlin idioms documentation. Bookmark it — it's the fastest way to keep swapping Java habits for Kotlin ones.
Now Go Write Some
You don't learn idioms by reading about them — they stick through your fingers, not your eyes. The fastest way to make them automatic is to solve small problems with them, deliberately reaching for the Kotlin way each time.
JetBrains keeps a great low-stakes playground for exactly that: solving Advent of Code puzzles in idiomatic Kotlin. Each walkthrough takes a real puzzle and solves it the way an experienced Kotlin developer would — collection operations, scope functions, and the idioms above, applied for real. Try a few yourself first, then compare. The gap between your solution and theirs? That's the accent.
Stop Translating, Start Trusting
The hardest part of learning Kotlin isn't the syntax. It's unlearning the defensive habits Java taught you, and trusting the language to carry weight you're used to carrying yourself.
Idiomatic Kotlin isn't about being clever or writing the fewest characters. It's about writing code that says what it means, so the next person — often you, months later — understands it at a glance.
Every language rewards speaking it natively. Kotlin is no different: once the accent fades, you finally get the safety and clarity you switched for.