Why Java Programmers Will Love Kotlin — Part 1

Yongtze Chi
8 min readNov 28, 2019

In the past two years, I’ve encountered the buzz about Kotlin the programming language more and more. Kotlin is not a new programming language at this point in time (Nov 2019). It was created in 2011 by the popular IDE maker, JetBrains, to build their IDEs. Its adoption has not been that great until Google announced first-class support for it on Android, and later as the officially preferred language for developing Android app in May 2019. According to a survey conducted by InfoQ published in Oct 2019, 16% of over 7000 participants say they are using Kotlin, with an additional 10% say they plan to adopt.

This trend certainly piqued my interests. I have to confess, I’m a long time Java user, and I’m very productive with it. There’s very little reason for me to take the productivity hit that comes with adopting a new language for my day to day work, unless the language is just that good. Nonetheless, I decided to find out for myself why Kotlin has attracted so much interests? And the result? I fell in love with it. I’m so glad I spent the extra time to learn Kotlin. Let me explain why I think all Java programmers should at least give Kotlin a look:

  1. Write Less Code
  2. Write Robust Code
  3. Write Beautiful Code

Let’s look at reason #1 more closely below with some examples. We’ll look at #2 and #3 in subsequent posts.

Reason 1: Write Less Code

Kotlin was designed based on valuable lessons learned from many other programming languages that come before, including Java. While it strives to become very Java like with great interoperability with Java, it has also made many improvements on the shortcomings of Java. The syntax should look familiar to Java programmers, and some simplifications on syntax will likely entice Java programmers.

Data Classes

In Java, how many times have we created a POJO to hold data retrieved from database, or submitted by user, or exchanged with external web services? How many of us will say they love to write getter and setter functions for all the properties? Every time you write a POJO, have you ever wondered if there’s a better way? Here’s how a POJO class looks like in Java:

// Java
public class Customer {
private Long id;
private String firstName;
private String lastName;
private String email;
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return this.id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return this.firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return this.email;
}
}

Here’s how the same class looks like in Kotlin:

// Kotlin
data class Customer(
val id: Long,
val firstName: String,
val lastName: String,
val email: String
)

And that’s all! Isn’t that much smaller than the Java version? What we just saw is a special class in Kotlin called data class. It’s basically a POJO with some extra magic. Users of the popular Lombok library might be familiar with this concept.Kotlin basically bake in the features provided by Lombok into the language itself. With data class, we declare the properties, and Kotlin compiler will generate the getters, setters, equals(), hashCode(), and toString() functions. The resulted class behaves like regular POJO, so, it’s very interoperable with other Java code.

Ohh, and by the way, properties access in Kotlin is done through the intuitive “.” (dot) operator:

// Kotlin
val customer = Customer(1L, "John", "Doe", "john.doe@domain.com")
val message = "Hello " + customer.firstName + " " + customer.lastName + "."

That’s a slightly cleaner than the Java version:

// Java
Customer cusomter = new Customer(1L, "John", "Doe", "john.doe@domain.com");
String message = "Hello " + customer.getFirstName() + " " + customer.getLastName() + ".";

Type Inference

In Kotlin, variables are declared using either the val (immutable variable) or var (mutable variable) keyword, with the data type specified after the variable name like this:

// Kotlin
val name: String = “John Doe”

A lot of times, we can omit the type altogether, and let Kotlin infer the type automatically:

// Kotlin
val name = "John Doe" // String
val list = listOf(1, 2, 3, 4) // List<Int>
val names = listOf("Name1", "Name2", "Name3") // List<String>

Compared to Java:

// Java
String name = "John Doe";
List<Integer> list = Arrays.asList(1, 2, 3, 4);
List<String> names = Arrays.asList("Name1", "Name2", "Name3");

This is especially handy when we have a function that returns non-trivial nested generic types. For example, let’s say we have the following function in Java:

// Java
Map<String, List<Customer>> groupByLastName(List<Customer> customers) { … }

Now, to store the result, we need a variable like this:

// Java
Map<String, List<Customer>> result = groupByLastName(customers);

That’s a lot of typing. Compared that to Kotlin’s syntax:

// Kotlin
val result = groupByLastName(customers)

It’s just a bit more fluent to write, and a bit less mental work.

Named Function Parameters with Defaults

One common pattern we see in Java is overriding a function with different combinations of parameters in order to provide sensible defaults for common cases. This has the side effect of needing to create the various versions of functions. Take for example, let’s say we have a compute function that requires 2 parameters, 1 of which can be defaulted. In Java, this is how we would implement the compute function:

// Java
public int compute(int value) {
return compute(value, 10);
}
public int compute(int value, int divisor) { … }

In Kotlin, we can implement the same thing with just one function using parameter default:

// Kotlin
fun compute(value: Int, divisor: Int = 10): Int {
// compute result

}

The benefit is even more obvious for functions with more parameters. Take the joinToString() String function that comes standard with Kotlin:

// Kotlin
fun <T> Sequence<T>.joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "…",
transform: ((T) -> CharSequence)? = null
): String

As you can see, every parameter has a default. That means we could have called joinToString() without provided any parameter values at all if the default is what we want:

// Kotlin
val list = listOf("A", "B", "C")
val str = list.joinToString() // produce "A, B, C"

Or, we can override the separator parameter:

// Kotlin
val str = list.joinToString("-") // produce “A-B-C”

What if we also want to override the postfix parameter? Easy, just name the parameter:

// Kotlin
val str = list.joinToString(separater = "-", postfix = ".")
// produce “A-B-C.”

Named parameters feature not only makes overriding default parameter values easy, it also helps readability of function calls tremendously, especially for functions with many parameters.

Smart Casts

In Java, when we write a generic function that takes a more generic data type, we often need to cast the value into more specific type in order to operate on it. For example,

// Java
int length(Object value) {
if (value instanceof String) {
return ((String) value).length();
} else if (value instanceof Integer) {
return (int) value;
} else {
return -1;
}
}

This is made a lot simpler in Kotlin with the smart cast feature:

// Kotlin
fun length(value: Any): Int {
if (value is String) {
return value.length // we are sure value is String here
} else if (value is Int) {
return value // and value is Int here
} else {
return -1
}
}

Notice there isn’t any explicit casting required. That’s because Kotlin compiler is smart enough to know that once we’ve checked the type in the if condition, we can be sure the type is String or Int in the code block. Hence, the compiler will generate code as if the type is casted correctly.

Kotlin will also cast the type automatically if we use the value on the right hand side of && or || operators:

// Kotlin
if (value is String && value.length > 10) { … }

No more redundant type casts. The result is cleaner and more readable code.

No Checked Exceptions

When Java was first introduced, checked exceptions was hailed as a best practice that the language enforces. However, over the years, the trend has definitely shifted against checked Exceptions. Don’t get me wrong, exceptions handling is still very important for creating robust program. But, I believe over the years, the industry at large has learned that checked exceptions do not necessarily ensure proper exception handling or better code quality.

Why is that so? Well, while declaring possible exceptions that a function can throw seem like a good idea at first, but it causes unnecessary try/catch blocks for larger applications. Take for example a function that fetches data from the database using JDBC. Anyone who has used JDBC functions will know that SQLException is thrown in almost every JDBC function. What do we usually do? Rethrow the exception, or wrap it in another exception. (Hopefully no one writes code that silently swallow the exception.) And so, every function in the call chain will have to deal with this SQLException.

In this example, what we really want is to handle the exception at the right place of the call chain, which is usually not in the place where the exception originates. But, forcing each function in between exception origination and proper handler to deal with the SQLException does not really promote better quality code. On the other hand, it increases the amount of boilerplate code through unnecessary try/catch blocks or throw declarations.

This problem is acknowledged by the Java community in general, so much so that the most popular application framework in Java: Spring Framework decided to automatically wrap SQLException as unchecked exceptions in their JdbcTemplate interface, and every other interfaces that deals with JDBC thereafter.

Kotlin basically took the lesson learned, and decided to eliminate checked exceptions altogether in the language from day one. All exceptions in Kotlin inherits from Throwable interface, which means they are not checked exceptions. Exceptions can be thrown anywhere, and should be handled in the right place. But, every function in between those two in the call chain does not need to care about the exception.

String Templates

String templates is not a new idea by any means. A lot of languages already have this feature before Kotlin comes along. Even in Java, String template is made available through various libraries, including the printf() and format() functions that comes standard since Java 1.5. However, Kotlin’s implementation is more natural and elegant compared to Java.

Here’s how we interpolate variables into a String in Java using String.format():

// Java
int age = 27;
String message = String.format("My age is %d.", age);

Note the required use of “%d” format placeholder, and why do we have to specify the variable as separate parameters? What if the number of placeholders don’t match the number of parameters supplied? What if the placeholder type does not match the variable type? The compiler cannot tell you about any of these problems.

Contrast that with Kotlin’s String templates:

// Kotlin
val age = 27
val message = "My age is $age."

It’s just so simple and straightforward. Moreover, because this is a native language feature, Kotlin compiler can actually do static analysis on your variable to tell you about issues like non-existence of the variable referenced, automatically converts the variable to String, etc.

Even better, you can even use expressions directly:

// Kotlin
val numberOfRows = 10_000
val totalTimeTakenMs = 2000L
val message = "Number of rows: $numberOfRows, total time: $totalTimeTakenMs ms, throughput: ${numberOfRows * 1000.0 / totalTimeTakenMs} rows/sec"

And just as you’d expect, the Kotlin compiler will tell you if the expression is valid or not. This is just slightly better and cleaner implementation than Java’s template functions.

Summary

In this post, we’ve looked few ways that Kotlin allow us to write less code compared to Java through features like data classes, smarter type inference, smart casts, elimination of checked exceptions, and native string templates.

In the next couple posts, we’ll look at how Kotlin enable us to write robust and elegant code. Stay tuned.

--

--