Back in '98 when I started with my CS education Java was the first programming language we were taught. Before I had experimented with QBasic, C and assembly but that was the moment where I started to really grow into being a software engineer. While I made something excursions to other languages; PHP, C#, JavaScript and even ColdFusion, Java always was my 'base' where I returned to. My great love as it were. But now she’s fallen out of fashion and I’m moving on to greener pastures: Kotlin.
Introduction
You might notice quite a big gap between this post and the previous one. There are multiple reasons for this (a project taking up a lot of my energy is one of them), but a big reason is that in my personal projects I don’t really feel like using Java anymore, but on the other hand Java is the common theme in most of my posts here. This is also reflected in the traffic my blog is getting; the "how to do X" type posts are the most popular.
My definitive move towards Kotlin started in November last year with the (awesome, check them out!) Advent of Code contest of 2017. I use these to challenge myself to learn new things. Back in 2015 I used the challenge to learn Scala and last year I started using Kotlin to solve the 2017 challenges (link if you want to check them out).
While doing the 2017 challenges I was actually having so much fun with Kotlin that I started working on the 2016 challenges as well!
Java
Likes
Don’t get me wrong: I still like Java. It’s just as strong a language as it was a year ago and the improvements being made are excellent. Having used local type inference in Scala and Kotlin I feel it’s a great idea. The Java 8 additions, streams and lambda’s, make me never want to do a pre-8 project ever again. I strongly prefer strongly typed languages for anything non-trivial; I feel they make me more productive.
But more importantly; what I like about Java is the ecosystem and community. Its ecosystem fully embraced open source and it is reflected in many mature, well written frameworks and libraries that help us Java developers be as productive as a developer can be. I strongly disagree with claims that dynamic languages make you more productive; the small amount of characters saved has to be paid for with much more extensive tests for all the run-time errors your compiler can’t help you with.
In the Java ecosystem there’s never just one way to do something. Just have a look at the amount of microservice frameworks available. While you can get caught with a case of analysis paralysis if you’re not careful, in my opinion the more choice the better!
Last but not least; the JVM is a beast. Java is fast. Does multithreading really well. So as a Java developer it is relatively easy to add a lot of value while still delivering safe and performant applications.
Dislikes
Java has a reputation of being overly verbose. The Java stewards are careful in taking new developments into consideration. With good reasons too; the Python 2 versus 3 split shows the effect that huge breaking changes can have. But while I do understand the reasoning behind this, I do feel that some changes are done too slow or in a poor fashion. It took Oracle a long time to add more functional constructs to Java, long after C# got them. Only now with Java 10 we’re getting local type inference, but I will probably never understand the omission of 'val' in favour of 'final var'. Both Scala and Kotlin already showed that this works just fine.
This can be seen in the community as well; the Java community can be quite conservative. There are still developers who are vehemently against the new lambda syntax for example. And local type inference is a hotly debated matter on reddit.
The standard API is also showing its age. As an example; Java 9 finally introduced convenience factory methods for Lists, Sets and Maps (So we no longer have to use Collections.singletonList!), but especially the one for Map is rather limited. Instead of introducing a Pair<A,B> generic which could then be used as a vararg to build a map, the Map.of factory method only goes up to 10 pairs. More and you’ll have to use Map.ofEntries instead.
By itself this is just a small nitpick, but overall these small annoyances add up.
Kotlin
I’ve tried out numerous other languages. Scala for example, but also Go and I even gave Node.js a shot. But most of these were 'missing' something. Generally it was the ecosystem; Scala came pretty close the the quality I was used to but for most ecosystems, Go and Node.js specifically, the tooling was horrible. It was not just a matter of them being immature but tooling moving in a completely wrong direction (for example pulling master branches directly into production code in lieu of 'dependency management').
Kotlin on the other hand did scratch the 'new and exciting' itch, while letting me access all of the ecosystem I’ve grown to accustomed to (and fond of). Unlike Scala, the interop works both ways: Kotlin code can use Java code, but the reverse is also true: Java code can use Kotlin code just fine. So, let’s discuss some of the things I really like!
Null Safe Type System
Let’s just start with the big one; Kotlin is null-safe by design. While you can have nullable values, it is not the default, and it is always a conscious decision. Since it is part of the type system itself in most cases null checks are not needed anymore. It removes a lot of ObjectUtils.allNotNull()
boilerplate.
What’s also cool is how smart the compiler is. A (contrived) example:
fun someFunction(myValue: String?) {
if(myValue == null) {
return
}
println(myValue.take(10))
}
In the example above I can just access myValue; the compiler knows that at that time it can’t be null.
No Checked Exceptions
Sometimes called Java’s biggest mistake. While I disagree on that (nulls are bigger) I would probably say it is probably the second biggest. All the way back in 2000-something one thing I preferred with C# was that every exception was a runtime exception. While checked exceptions sound good on paper, being forced to handle stuff, in most cases it is just extra boilerplate. For beginners it might be good to be told constantly that when you’re opening a file something can go wrong, in my experience developers tend to develop a feel for this quite fast. This has led to a weird trend in the Java ecosystem where you often see checked exceptions being wrapped in unchecked ones.
Immutable By Design
Every reference in Kotlin is either a variable or a value (or a constant but I’ll leave that out for now). A variable can be reassigned after its definition. A value can’t. So as a developer you always make a simple choice whether something is going to change somewhere down the line or it doesn’t. Same amount of characters so no 'final' clutter anywhere:
val a = 1
var b = 2
b = 3
Simple and clean.
The same applies to collections. List<>, Set<> and Map<> are all immutable. If you want a collection that you can add to, you need to explicitly use the mutable versions. This can be done by using the builder methods:
val a = listOf(1, 2, 3) //Immutable
val b = mutableListOf(4, 5, 6)
b += 7 //b is now [4, 5, 6, 7]
Or by creating a copy:
val a = listOf(1, 2, 3) //Immutable
val b = a.toMutableList()
b += 7 //b is now [1, 2, 3, 7]
Local Type Inference
One of my biggest gripes with the Java language maintainers is the weird 'designed by committee' choices they make. While I understand their struggles, it would have been so awesome if they would have adopted both var and val in Java 10. Unfortunately we’re now locked out of that permanently, in Java 10 you either use 'var' or you use 'final var'. And it can also only be used within methods; so it is not consistent at all.
Anyway; with Java 10 we now also have local type inference which added the 'var' keyword. Kotlin (and many many other languages) had this for a while now. I think it is strange to hear people complain about local type inference in that it would make your code less readable; in my opinion if you need the type of a variable within a method to understand what it is/does it is a clear sign your code is not well written. I’ve seen code like this quite often:
List<Person> list = someService.findPersons();
list.forEach(p -> doSomethingWith(p))
That’s just bad naming. Especially a few lines lower it is probably not clear anymore what 'list' contains. The Kotlin equivalent, but with proper naming:
val persons = someService.findPersons();
persons.forEach { doSomethingWith(it) }
With good consistent naming local type inference makes code more clean, concise and readable in my opinion. Kotlin still leaves the option to specify the type, which is sometimes needed if the type can’t be inferred. An example:
val persons: List<Person> = someService.findPersons();
persons.forEach { doSomethingWith(it) }
And of course your IDE can always tell you the type of something if you really need to know, so in my opinion it is just a must-have in a modern language.
Smart Casts
Kotlin has smart casting. This means that within the scope of a type check the compiler knows the type of an object. While casting doesn’t happen often, it is convenient none the less:
fun makeSound(animal: Animal) {
if(animal is Cow) {
animal.moo()
}
}
The compiler knows that within the if block the animal is actually an instance of Cow and you can call Cow-specific methods without having to cast. The compiler did it for you.
String Templating
Where in Java you’d often use String.format to format strings with variables, Kotlin has string templating built in. An example:
val name = "Jill"
val grades = listOf(90, 80, 70)
println("$name has ${grades.size} grades with an average of ${grades.average()}")
As you can see, if you want to just include a variable directly you can just prepend a $ and it is included. If you want to do somewhat more complex stuff you can use the ${} format to include pretty much anything.
Multi-line String Constants
And when you have awesome string templating, you also want convenient multi-line strings. And of course, Kotlin has it. Frankly, this is one of the things where I simply do not understand why this has not been in Java for a decade; sometimes it makes you wonder if the language designers actually use the language.
Multi-line strings are extremely nice in tests, for example if you want to post some JSON to an endpoint:
val someValue = 42
val json = """
{
"someValue": $someValue,
"doubledValue": ${someValue * 2}
}
"""
post(json)
In Java I generally advocate for storing test JSON outside the test classes. Because Kotlin has multi line strings that support quote characters just fine inline JSON is perfectly readable.
Automatic Properties
Kotlin recognizes getters and setters as properties, even if they are inside Java classes. This is again handled by the smart compiler for you. Normally in a Kotlin class you define properties as properties:
class MyClass(val name: String, private val number: Int)
val instance = MyClass("Jill", 42)
println(instance.name)
You can actually define a class inside a method and you don’t need to include braces if you don’t have anything to put inside of it. This shows a simple class with name and number members. Name is available as a property on the outside, number is not, since it is private.
But let’s say we have a mixed codebase and/or are using a Java library. How does this work? Well; Kotlin’s compiler creates properties for you. A Java class:
public class MyJavaClass {
private final String name;
private final int number;
public MyJavaClass(String name, int number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
}
First of all; just look at the difference in boilerplate you need to create a simple Pojo when you’re not using Lombok!
This class can be used inside Kotlin the exact same way:
val instance2 = MyJavaClass("Jill", 42)
println(instance2.name)
In neither case this property is assignable; it is read only.
Short Hand Function Definitions
In Kotlin, functions can have a long and a shorthand form. The latter is suitable for simple functions that just operate on their inputs. As an example, the long version of a multiply function:
fun multiply(a: Int, b: Int) : Int {
return a * b
}
Can also be expressed as:
fun multiply(a: Int, b: Int) = a * b
No need for braces, a return statement or return type; it is inferred from the result of the expression after the =
.
Default and Named Arguments
Function arguments can have defaults. It is something that’s included in a lot of languages. Java, unfortunately, isn’t one of them. Kotlin however, as awesome as it is, does have them:
fun divide(a: Int = 1, b: Int = 1) = a / b
divide() // 1
This can save you from having to implement a lot of function overloads with different amounts of arguments.
What’s also neat is that arguments can be passed in any order if you name them:
divide(b = 10, a = 1000) // 100
Data Classes
Finally we can just get rid of Lombok! In a previous project it was one of the selling points for us for Kotlin. We used Lombok extensively for DTO’s/Domain classes to generate getters, setters and constructors and Lombok was the main factor in preventing us from updating to Java 9.
A data class in Kotlin is used to model plain objects that mainly are there to model data or state. Normally they have no behaviour. A POJO (or in this case, POKO!). An example data class:
data class Pet(val name: String, val age: Int)
Just a simple, immutable (if you want a mutable data class you can use var over val), class that contains a Pet’s data. What’s nice is that you also get some stuff for free this way. A data class will get toString()
, equals()
, hashCode()
methods implemented for you. Also, you get a nice copy()
function that you can use to create a modified copy of an immutable data class:
val myData = Pet("Max", 6)
val myNewData = myData.copy(age = 6)
What’s also really nice is that these data classes are incredibly suitable to be used as DTO’s in a REST interface. Both as output and input. Jackson Databind can, with the Jackson kotlin plugin, deserialise JSON (or YAML or XML) into these data classes for you.
Destructuring Declarations
Another nifty feature that data classes tie into is destructuring declarations. What’s that? Quite simple. Ever seen code like this:
val pet = petService.findPet("Max")
val name = pet.name
It’s not uncommon to get some kind of return value where you want to actually use just one property in a lot of places, so you store it in a separate value for user later. In Kotlin you can do this instead:
val (name, age) = findPet("Max")
Any object that has componentN() functions can be decomposed into its separate components. You can create these functions (marked as operators) yourself for any class, but data classes get them for free as well. Local type inference works here too; name is a String and age an Int, because those are the return values of Pet.component1()
and Pet.component2()
respectively.
If you’re not interested in one of the values you can use the underscore like in Scala:
val (name, _) = findPet("Max")
Now only name will be available. Many built in types and collections also support this:
val (a, b) = listOf(1, 2, 3)
This gets the first two elements of a List as a and b. What happens if you destructure a list with too few elements? It throws an IndexOutOfBoundsException
.
If/When (switch) Expressions
In Kotlin if
and when
(switch) blocks are not statements, they are expressions. So they can be used inline for their return value. A simple example is how this has replaced the ternary operator in Java:
fun defaultIfNull(s: String?, default: String) = if(s == null) default else s
Note
|
The above is quite redundant by the way, you can do these types of null checks with the elvis operator as well: val myVal = s ?: "default"
|
Again you see how this also ties in well with shorthand notation of functions. You can do the same for when (Kotlins version of switch) statements. Let’s define an enum of colours we want to map to their hex codes:
enum class Color {
RED,
BLUE,
GREEN
}
We can write a map function like this:
fun mapColor(c: Color) = when(c) {
Color.RED -> "#FF0000"
Color.GREEN -> "#00FF00"
Color.BLUE -> "#0000FF"
}
I personally love how clean and readable this is.
The rules are simple; if you want to use an if or when block as an expression all paths need to be covered. So in the case above the compiler would not accept it if I left out the Color.RED map. The same is true for an if-else-if statement; it must be clear to the compiler that all paths are covered.
Functional Programming
While I would not class Kotlin in the FP category of languages, it does an wonderful mix of OO and FP principles that makes it easy and convenient for programmers to use a functional approach in handling their logic. It almost pushes you towards decomposing the problem into small functions with a single responsibility. It pushes you towards immutable data classes and collections that are null-safe. And then lets you compose all this into a solution by applying your functions on collections of data through map and reduce functions.
Kotlin pushes you towards immutable collections. In our day to day work it is actually not that often that we modify a collection in place. In most of my projects a lot of the logic of getting something from a database, changing it a bit and then outputting it to JSON can typically be expressed as map operations on a stream of data.
Where in Java you have to call .stream() on any collection and then later .collect() it again, Kotlin handles this for you. An example in Java:
var numbers = List.of(1, 2, 3);
var doubled = numbers.stream().map(n -> n * 2).collect(Collectors.toList());
We create a list of numbers and then double them. Fortunately we can use Java 10, if you’re stuck on 8 it looks like this:
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Integer> doubled = numbers.stream().map(n -> n * 2).collect(Collectors.toList());
4 years ago I thought the above was awesome (so much better than writing a for-each loop and stuff it into a new collection. But now in Kotlin I just want to do this:
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 }
Clean; stripped of all boilerplate.
Keep in mind though, a .map
on a collection is eager. If you do multiple map operations in a row, consider converting them to a sequence (with .asSequence()
), like so:
val l = listOf(1, 2, 3, 4)
val mapped = l.asSequence()
.map { it * 2 }
.map { it * 2 }
.toList()
Quality of life
I can actually go on for quite some time with more fun stuff. Kotlin adds sensible operator overloading (you can now just add two BigIntegers together with + or add two lists together). Lazy loading of data. Infix functions. Extension functions (want to add a .toHexadecimal()
method to List<Int>
? You can!). Global functions. Pairs. The list goes on. There is just so much nice stuff there. If a collection has an isEmpty()
function it will have the inverse isNotEmpty()
too. You can write a reduce function to get the sum of a collection of numbers, but there is no need to, it is all built in.
And this is what made me switch. Kotlin is obviously made by developers to solve the problems they encounter when working with Java. When working in Kotlin and then going back with Java makes Java feel stale, old and more 'designed by committee' than created for developers. The Kotlin developers obviously took a good look at Java, Scala, C#, Python and a ton of other languages to see what works and what doesn’t. Kotlin is an amalgam of the 'it works' bits of the languages while at the same time they managed to not fall into the same traps these languages did fall into.
Conclusion
I love trying out new languages and frameworks. Some I have positive experiences with (Scala for example), some were mostly negative (Go and Node.js). Kotlin is different in that there is nothing that really bothers me, and I’m someone that is easily bothered by inefficiencies. Kotlin doesn’t attempt to reinvent wheels poorly; they didn’t bother creating their own build system. Maven and Gradle work just fine. They didn’t get bored of developing Kotlin half way through and ended up with something like Go. For me the Kotlin is the perfect everyday language: it solves roughly all the problems I have with Java and on top of that gave me a ton of goodies I never knew I wanted until I started using them.
And most importantly; we still have access to the entire Java ecosystem. Java’s ecosystem in my opinion is unique in its maturity and openness. Libraries are in most cases of high quality and have a solid professional community working on them. Spring, the de-facto enterprise framework, has picked up Kotlin as a first class citizen. The Android community has picked Kotlin as the primary language over Java. And Java itself is profiting from the competition; in the last few versions we got local type inference for example. Competition is good for progress.
What is also nice is to see that, with many senior Java engineers being enthusiastic about Kotlin, dev managers are also slowly warming up to the idea. The two way interop here is a killer feature; you can slowly add Kotlin to your system without having to convince someone you will need to rewrite a large part of your application, 'just because'. In my opinion Kotlin is more of a Java dialect than a separate language. Aside from the compiler there isn’t anything you need to change. It actually works fine on Java 8; which can be a selling point for some companies who are still stuck on 8.
If you are a Java developer and haven’t given Kotlin a serious try by now; give it a shot. Push through the initial hard bit where you are not familiar with the syntax. Especially when you are using IntelliJ the transition is very smooth.