esc
Anthology / Yagnipedia / Kotlin

Kotlin

The Apology Letter Java Should Have Written Itself
Technology · First observed 2011 (JetBrains, Andrey Breslav — but emotionally felt since 2003) · Severity: Diplomatic (causes no harm, inspires mild guilt in those who decline)

Kotlin is a statically typed programming language for the JVM, designed by JetBrains in 2011, which fixes nearly everything wrong with Java — the verbosity, the null pointer exceptions, the ceremony, the boilerplate — while changing absolutely nothing about the architecture that made those problems systemic in the first place.

This is not a criticism. It is an observation. The observation is: Kotlin is a beautiful renovation of a building with a cracked foundation. The countertops are marble. The plumbing works. The light is excellent. The building is still the cathedral that Design Patterns built and Oracle owns, sitting on the JVM, surrounded by Spring, sinking slowly into the same geological stratum the renovation was supposed to escape.

Kotlin is what Java would be if Java had been designed by people who had used Java.

“The migration away from Java began slowly — Scala, Kotlin, Clojure, all running on the JVM, all trying to fix Java without leaving the ecosystem.”
Java

The Best Friend’s Language

Every developer knows a Kotlin enthusiast. Not an evangelist — evangelists are loud and eventually alienating. A Kotlin enthusiast is a friend. A good friend. The kind who genuinely wants to help. The kind who texts you an article about null safety at 11 PM and follows up the next morning with “did you read it?” The kind who says “just try it” with the earnest warmth of someone offering you a home-cooked meal, and who is, objectively, correct that the meal is delicious.

The friend is right. Kotlin is better than Java. This is not disputed. It is not even debatable. Data classes replace two hundred lines of boilerplate with one line:

data class User(val name: String, val email: String)

In Java, this is:

public class User {
    private final String name;
    private final String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }

    @Override
    public boolean equals(Object o) { /* 12 lines */ }

    @Override
    public int hashCode() { /* 4 lines */ }

    @Override
    public String toString() { /* 3 lines */ }
}

Kotlin’s version is shorter. Kotlin’s version is correct. Kotlin’s version generates the same bytecode. The friend is right. The friend is always right. That is what makes respectfully declining so difficult.

The Respectful Decline

The respectful decline is not about Kotlin. The respectful decline is about the JVM.

The JVM is a virtual machine that starts in seconds, runs a garbage collector that sometimes pauses, and deploys as a JAR file that requires another JVM to run it, which requires a specific version of that JVM, which requires a container to isolate the version, which requires an orchestrator to manage the containers. The ceremony that Kotlin removed from the language remains in the deployment.

Go produces a binary. The binary has no runtime. The binary has no dependencies. The binary runs. The deployment is scp. The ceremony is zero.

Kotlin fixes Java the language. Go fixes Java the experience. The language was never the whole problem. The language was the part of the problem you spent the most time looking at, which made it feel like the whole problem, the way a leaky faucet feels like the whole problem until you notice the foundation crack.

The respectful decline goes: “I know. I’ve seen it. It’s genuinely good. I chose Go.” The friend nods. The friend understands. The friend sends another article about coroutines at 11 PM anyway, because the friend is a friend, and friends do not give up.

What Kotlin Gets Right

To be clear — and the clarity matters, because the respectful decline must not be confused with dismissal — Kotlin gets almost everything right:

Null safety. The billion-dollar mistake, caught at compile time. String cannot be null. String? can. The compiler enforces this. A generation of NullPointerException stack traces, prevented. This alone justifies Kotlin’s existence.

Extension functions. Add methods to existing classes without inheritance, without wrapper classes, without the UserUtils static method cemetery. The function looks like it belongs to the class. The compiler rewrites it as a static call. Elegant.

Coroutines. Structured concurrency that is — and this must be acknowledged — dramatically more approachable than Rust’s Pin<Box<dyn Future<Output = Result<T, Box<dyn Error + Send + Sync>>> + Send + 'static>>. Coroutines launch, suspend, and resume with syntax a human can read. Not as simple as goroutines, but nothing is as simple as goroutines. Goroutines are go f(). Everything else is an apology for not being that.

Sealed classes. Algebraic data types that the compiler can exhaustively check. when expressions that guarantee every case is handled. Pattern matching that works.

Interoperability. Kotlin calls Java. Java calls Kotlin. The migration path is incremental, file by file, class by class, which means it is actually possible, unlike Rewrite, which is never possible.

All of this is real. All of this is good. None of this changes the fact that the binary still runs on the JVM, the JVM still needs a container, and the container still needs Kubernetes, and the Go developer has already deployed and gone home.

The Scala Lesson

Scala tried this first. Scala ran on the JVM, fixed Java’s verbosity, added functional programming, and produced code so clever that only Scala developers could read it — and not all of them. Scala proved that making the JVM’s surface language more powerful does not make the JVM’s ecosystem simpler. It makes the language a monad and the build system a PhD thesis.

Kotlin learned from Scala’s mistakes. Kotlin is simple where Scala is clever. Kotlin is practical where Scala is theoretical. Kotlin is readable where Scala requires a type theory textbook. This is genuinely admirable. The JetBrains team looked at Scala and said “yes, but for humans,” and they were right, and the result is a language that any Java developer can learn in a week and prefer within a month.

The problem is that “better than Java on the JVM” and “better than Go with a single binary” are different competitions, and the second one has a lower bar and a faster deployment.

The Android Exception

One context exists where the respectful decline does not apply: Android.

Android runs on the JVM (or its descendant, ART). Android’s alternative to Kotlin is Java. There is no Go option. There is no single-binary option. There is the JVM and whatever language you choose to write for it, and in that constrained competition — Java versus Kotlin, no other contestants — Kotlin wins unanimously, totally, and without qualification.

Google made Kotlin the official Android language in 2019. This was correct. On Android, Kotlin is not a renovation. Kotlin is the only reasonable way to build. The friend who advocates Kotlin for Android is not just right but urgently right, and the respectful decline does not apply, and the 11 PM article about coroutines should be read immediately.

The respectful decline applies only on the server, where the JVM is a choice, and other choices exist, and one of those choices is a gopher carrying a single binary and a lizard carrying fifty years of knowing that simple wins.

The Caffeinated Squirrel’s Opinion

The The Caffeinated Squirrel adores Kotlin. This should be noted. The Squirrel finds Go insufficiently expressive, insufficiently clever, insufficiently interesting. The Squirrel wants sealed classes and extension functions and scope functions (let, apply, also, run, with — five ways to do the same thing, each subtly different, which the Squirrel considers richness and the Lizard considers five ways to be wrong).

The Squirrel’s Kotlin code is beautiful. It is concise. It is idiomatic. It uses every feature the language offers. It runs on the JVM, in a container, managed by Kubernetes, deployed by a CI/CD pipeline with fourteen stages.

The Lizard’s Go code is plain. It uses if err != nil. It compiles to a binary. It runs.

They both serve the same twelve hundred users.

The Measured Difference

Metric Kotlin Go
Lines to fetch a user from a database 15 12
Null safety Compile-time Not applicable (no null)
Deployment artifact JAR + JVM + container Binary
Startup time 2-8 seconds (Spring) 10 milliseconds
Memory at idle 200-500 MB (JVM) 10-30 MB
Build system Gradle (XML or Groovy or Kotlin DSL) go build
Time to explain deployment to a junior 45 minutes 2 minutes
11 PM articles from best friend Weekly Never (Go is not interesting enough to text about)

The Lizard’s Scroll

The Lizard, presented with a Kotlin data class and a Go struct that did the same thing, blinked once:

THE RENOVATION IS BEAUTIFUL
THE FOUNDATION IS THE SAME

THE LANGUAGE FIXED THE LANGUAGE
THE ECOSYSTEM KEPT THE ECOSYSTEM

THE FRIEND IS RIGHT
THE FRIEND IS ALWAYS RIGHT
THE FRIEND IS RIGHT ABOUT THE COUNTERTOPS

THE GOPHER IS RIGHT ABOUT THE FOUNDATION

See Also