by Adam Arold
Kotlin pitfalls and how to avoid them
Kotlin is all the rage lately. And while I do agree that the language is well thought out, it does have — as with everything else — its flaws.
In this article I’ll explain some of the pitfalls I encountered and try to help you avoid them.
In Kotlin you can write your code as if
null never existed and this can make you forget that
null is omnipresent but it hides. Let's look at this simple and seemingly innocent class:
If you try to instantiate this, you’ll get a
bar tried to access the
c before it was initialized.
Of course the application logic was flawed here, but you still got an
Exception. The worst part of this is that your IDE won't complain about this.
The takeaway here is that Kotlin will help you in a lot of cases (nearly all) to avoid
null, but you can't forget about it and from time to time you'll encounter things like this.
nulls from the JDK
Kotlin’s standard library handles
nulls fine. But if you use classes from the JDK, you will have to handle possible
null pointers from library functions by hand.
Most of the time the Kotlin classes are enough, but sometimes you have to use something like the
In this case, you have to use the
!! operator. But in other cases the
null safety operator (
?) can also work. Nevertheless, if you use Java libraries extensively you'll have to litter your code with
?s or write adapters for Java classes. This is something you can't really avoid.
There’s another more hideous problem you might bump into. When using methods on JDK classes, they can return
null and don’t have syntactic sugar like the
Map access above.
Consider the following example:
In this case, you use
peek which in fact can return
null. But the Kotlin compiler won't complain so you can get a
NullPointerException if your
Queue was empty.
The problem here is that we used
Queue which is a JDK interface and if you look at the implementation of
It says that
peek will return
E which will lead Kotlin to believe that
E is not nullable. This might be worked around in a future version of Kotlin, but right now it is important to keep this in mind in your projects and use interfaces like this:
When a lambda has a single parameter you can omit it from your code and can use
“it: implicit name of a single parameter One other helpful convention is that if a function literal has only one parameter, its declaration may be omitted (along with the ->), and its name will be it.” — Kotlin docs
The problem with this is when you have nested functions like in this example:
It only takes a few
its to lose track which is which. The solution to this problem is to name the parameters explicitly:
Take a look at this
Data classes give you a bunch of functions and you can also make a
copy of them. Guess what this will print out:
This will print
foobar, wombar, oops. The problem is that while the name indicates that
copy will make an actual copy in fact it will only copy the references in your object. This can be insidious if you forget to write an unit test and you pass your
data classes around as if they were immutable value objects.
The solution to this problem is to pay attention to your
data classes and if they should be value objects make them one:
There is an other problem with
data classes: you can't tell Kotlin which fields you want to put in your
hashCode, you can only
overrideboth and write them by hand. Keep this in mind.
Take a look at this example:
If you use classes like this from other Kotlin projects the
internal keyword will be respected. If you look at this from a Java project however
hiddenOperation will be
public! To avoid this I'd suggest using
interfaces to hide implementation details:
Non-generic global extensions
The utility of extension functions is unquestionably high, but with great power comes great responsibility. You can — for example — write extension functions to JDK classes which will be visible for the whole project. This can be problematic when they are non-generic and represent operations which only make sense in a local context:
Now everybody on your project will scratch their heads when they bump into this. So I think it is good if you think twice before you write extension functions but they can be really powerful. here are some examples which might be useful:
Unit returning lambdas vs Java SAM conversion
When you have functions which accept lambdas you can omit the
return keyword if the lambda's return type is
This is fine but if you call this from Java you’ll face the awkward problem of needing to return
This is very clunky from the Java side. If you try to make this work from Java you can define an
then you can use Java’s SAM conversion to make this very simple:
but then from the Kotlin side it becomes a mess:
The problem is that Kotlin does not support SAM conversion for Kotlin classes and it only works with Java classes. My suggestion is that for simple cases just use Java’s built in SAM interfaces like
Java interop with unmodifiable Collections
Kotlin gives you immutable variants of the JDK’s collection classes:
This is a very nice addition but if you look at this from the Java side you’ll see the JDK’s
If you try to modify this
Set the same will happen as if you have used Java's
Collections.unmodifiableSet() method. I don't know whether this can be (or should be) worked around but this is something you can keep in mind when working with Kotlin's immutable versions of Java collections.
No overloads in interfaces
This is only an issue from an interop perspective, but Kotlin does not support the
@JvmOverloads annotation in an
interfaceand you can't use it on
Currently the only thing you can do to have overloads is to define them by hand:
Keep it in mind though that you can suggest improvements to Kotlin itself using KEEP (Kotlin Evolution and Enhancement Process). KEEP is something like JEP in Java, but of course KEEP has much less red tape compared to JEP.
Kotlin is a very popular language right now and I do agree that it is a Turbo Java, but you should take any hype with a grain of salt. As we have seen above Kotlin has its own pitfalls which you should be aware of if you plan to use it.
All in all I think that the problems mentioned above can either be worked around easily or not critical and they don’t limit the usability of the language itself.
Thanks for reading! You can read more of my articles on my blog.