skip to main content

Kotlin Is Java 2.0, But It's Still Java

published icon  |  category icon programming

tags icon java kotlin

In April, I took a few weeks to explore Go, claiming that “Go fixed Java/C#”. This month, I’m preparing an Android app development university course where another new programming language crossed my path: Kotlin, birthed by the fine JetBrains folks who are also responsible for many great development environments. Instead of skimming over the essentials—after all, the short course focuses on Android, not on the language API—, I wanted to get a more deeper understanding of the hows and the whys of Kotlin. With the guidance of a friend and Kotlin in Action, I can now say that I (somewhat) speak yet another programming dialect. Another win for polyglots!

Kotlin Is Java 2.0

Kotlin is a language that is not trying to reinvent the wheel, and that’s a good thing. Instead, its creators focused on the most prevalent pain points of classic (enterprise) Java development, and tried to alleviate these. Indeed, we’re talking about duplication and plumbing. Instead of using external libraries such as Google Guava to simplify collection manipulation, now several neat functions are built-in. Well… That’s a bit of a stretch: you still need the Kotlin runtime jar where those functions reside, so in that sense it’s still “merely” a library. For example, reading a resource is reduced from

String sql = new String(Files.readAllBytes(Paths.get(getClass().getResource("dbcreate.sql").getPath())));

to:

val sql = javaClass.getResource("dbcreate.sql").readText()

Digging through the readText source reveals it’s a wrapper around a fancy new concept called extension methods, that allow you to extend existing libraries with new functions. Want to add something to the String class? In Java, you can’t. In Kotlin, you can:

fun String.last(): String {
    return this[this.length - 1].toString()
}

fun main(args: Array<String>) {
      println("hi".last())
}

Note that the way functions are written have some similarities with Go: the argument name comes first, its type second. This takes a while to get used to but does improve readability by a large margin: another of Kotlin’s strengths. Oh, and semicolons have disappeared too. Finally.

Go ahead and copy-paste the above code in the online Kotlin Playground. This principle is (ab)used a lot to alleviate an absurd amount of duplication Java developers have to put up with, thanks to clever functions such as with, use, and apply. What do these exactly do? They eliminate the need to repeat yourself while trying to configure something like a transaction or a connection object. In Java, you’d do this:

TransactionManager manager = new TransactionManager("someBaseConfigfile.xml");
manager.setAutoCommit(false);
manager.setBulkStatements(100);
manager.setVerbosity(Verbosity.HIGH);

While in Kotlin, that’s reduced to:

val manager = TransactionManager("someBaseConfigfile.xml").apply {
    autoCommit = false
    bulkStatements = 100
    verbosity = Verbosity.HIGH
}

No need to repeat yourself with consecutive manager. calls. What actually happens here is that the { } block is a closure that’s the single argument of the apply() function, where the this context gets translated to the callable object. So, in essence, these tricks can be part of a Java utility library as well, except that here, they’re built-in. Furthermore, in Kotlin, getters/setters are translated into fields and used this way. autoCommit = false still results in a call to setAutoCommit(), but it requires less hassle.

Another very powerful inclusion is the when statement: Java’s switch on steroids. It can accept an argument—but without it, its body will check for boolean values—and automatically compares between object instances. For example, here’s an isItWorhtIt() function that compares the card from the argument with a few known ones to return a price:

data class Magic(val name: String, val rarity: String = "common")

fun isItWorthIt(card: Magic): Double {
    return when(card) {
        Magic("The Scarab God", "rare") -> 15.0
        Magic("Binding Mummy") -> 0.1
        else -> 0.0
    }
}

fun main(args: Array<String>) {
      val theScarabGod = Magic("The Scarab God", "rare")
      print(isItWorthIt(theScarabGod))
}

This would be impossible to do in Java, as it’s not a constant. Behind the scenes, the above code gets translated into a chain of if-else’s using .equals().

Kotlin Is Still Java

As I was trying to understand what happens under the hood with several of Kotlin’s mechanics, I decided to decompile the generated .class bytecode files. The Kotlin language is primarily written for JVM developers, but it can also output native code with the help of the LLVM compiler, or, as any respectable modern language can, output JavaScript. When we let the Procyon decompiler do its thing on the above simple string extension file, the Java output is as follows:

public final class MainKt
{
    @NotNull
    public static final String last(@NotNull final String $this$last) {
        Intrinsics.checkNotNullParameter((Object)$this$last, "<this>");
        return String.valueOf($this$last.charAt($this$last.length() - 1));
    }

    public static final void main(@NotNull final String[] args) {
        Intrinsics.checkNotNullParameter((Object)args, "args");
        System.out.println((Object)last("hi"));
    }
}

There, all the magic gone: it’s simply a stupid static method. Now how about data classes, another great example of squandered code duplication? As another example, consider a Person with a name and age. The Kotlin way of writing this is:

data class Person(val name: String, val age: Int)

That’s it—a one-liner to define a class! This comes with free toString(), equals() and hashCode() implementations, and all val properties defined in what is called a primary constructor are translated into immutable fields. Here’s the procyon output of the above class:

public final class Person
{
    @NotNull
    private final String name;
    private final int age;

    public Person(@NotNull final String name, final int age) {
        Intrinsics.checkNotNullParameter((Object)name, "name");
        this.name = name;
        this.age = age;
    }

    @NotNull
    public final String getName() {
        return this.name;
    }

    public final int getAge() {
        return this.age;
    }

    @NotNull
    public final String component1() {
        return this.name;
    }

    public final int component2() {
        return this.age;
    }

    @NotNull
    public final Person copy(@NotNull final String name, final int age) {
        Intrinsics.checkNotNullParameter((Object)name, "name");
        return new Person(name, age);
    }

    @NotNull
    @Override
    public String toString() {
        return "Person(name=" + this.name + ", age=" + this.age + ')';
    }

    @Override
    public int hashCode() {
        int result = this.name.hashCode();
        result = result * 31 + Integer.hashCode(this.age);
        return result;
    }

    @Override
    public boolean equals(@Nullable final Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Person)) {
            return false;
        }
        final Person person = (Person)other;
        return Intrinsics.areEqual((Object)this.name, (Object)person.name) && this.age == person.age;
    }
}

Sixty-one lines in Java, reduced to a single one in Kotlin: wow. The language is littered with cool stuff such as this (operators, default arguments, simpler functions, powerful pattern matching, destructing an object into pairs, …).

However. This easy interoperability with existing Java code comes at a price: in essence, you’re still limited to what the Java Virtual Machine can and cannot do. Everything in Kotlin is what you’d call syntactic sugar and gets translated into JDK 1.6/1.8/10/13/whatever compatible bytecode. This means that when the going gets tough, it is still very much required to have a firm grasp of the Java fundamentals.

My friend calls Kotlin Java 2.0. But it’s still Java. Sure, you can concentrate on the essence and leave the duplication and plumbing up to Kotlin. Sure, it produces much more readable code. Sure, it might reduce classic mistakes as immutability and nullable types are built-in. But it’s still Java! Compared to Go, there are still 4 ways to do one thing, there’s still the exceptionally complex (but native) JVM threading, and sometimes, you have to write very awkward things like ::class.java.resource to get hold of the static class instance.

Many languages live on top of the JVM: Groovy, Scala, Kotlin. Why choose Kotlin? Because unlike Scale, it’s a general-purpose multi-paradigm language: it promotes functional-style coding, but it doesn’t enforce it. Because unlike Groovy, it’s statically typed and its compiler does a very good job at smart casting and type inference. But most of all: because it’s gaining traction1: it is the Google-recommended way to develop Android apps. You don’t need to install anything if you have IntelliJ ready to go: a very smart move! My interoperability experiments with existing Java codebases was overall very positive. So, if you’re limited to the JVM, by all means, increase your productivity and happiness with Kotlin.

Just remember that there is life besides the JVM.


  1. There’s still a long way to go: the TIOBE index tells me Kotlin is the 38th most popular language, while Scala sits two seats above it, and Groovy is 15th. Guess which one is second. Java. Go is number 13, by the way! ↩︎

I'm Wouter Groeneveld, a level 36 Brain Baker, and I love the smell of freshly baked thoughts (and bread) in the morning. I sometimes convince others to bake their brain (and bread) too.

If you found this article amusing and/or helpful, you can buy me a coffee - although I'm more of a tea fan myself. I also like to hear your feedback via Mastodon or e-mail. Thanks!