Some years back Kotlin was an easy sell, because Java (lets say up to Java 9) felt dated and was inconvenient in many regards. But Oracle invested a lot to make Java more attractive.
Recent Java features:
Local type inference: var thing = factory.createThing()
records that are somewhat similar to data classes: record Point(int x, int y) {}
Pattern matching (no serious equivalent in Kotlin so far!)
More comfortable and concise creation of collections: List.of("a", "b", "c")
Multi-line strings
String templates (preview)
Direct execution of Java files (similar to running a script)
Sealed classes and interfaces
Virtual threads and structured concurrency
main method without the need to write a class around it (preview for now)
That are a lot of improvements!
What I still like more in Kotlin
null safety
Collection API
Functions as first class citizens
Extension functions
val (and not only var)
if as expression, especially compared to condition ? result1 : result2
Primary constructors
Easier and more natural way to define annotations
More powerful generics
Ranges
Type aliases and alias imports
Object literals
closed (final) classes by default
named parameters with default values
Besides that I like the syntax in some cases a bit more. For example == instead of .equeals(...), lambda expression outside of the parameter list (map { it * x }), or the miss of semicolons.
Things I miss in Kotlin
Pattern matching
Package-private visibility
use with more than one resource (try-with-resource in Java)
API documentation with a compact design and a search functionality
Your opinion?
What are your favourite Kotlin features that you would consider as an advantage over Java?
Java records are still clunky without named and default parameters, because there is still a need to use builders there. val can be taken from Lombok, but you still need to define types for class fields.
As for other features I like in Kotlin:
== usable with objects, not only primitive types
operator functions
delegation
better reflection
object destructuring (an addition of a deep destructuring would be nice)
Inlining which allows for: non-local returns, reification, “passing through” behavior (suspending, checked exceptions if Kotlin would have them)
Companion objects make much more sense to me than static. Only after using Kotlin I discovered the static context is a huge hack we introduced so long ago that everybody used to it and don’t consider it a hack.
(almost) Everything is an expression
Additionally, Kotlin compared to Java introduces some concepts or styles of programming, which are not features per se, but are encouraged and supported by multiple features:
Immutability - I rarely use var properties and almost no var locals. When I’m reviewing Java code and I see a standard pattern where a variable is defined before if and then two different values are assigned in both branches, I feel like this is a code smell. Then I realize there is no other way in Java… This style is supported in Kotlin by: val, “everything is expression”, scope functions, etc.
Top to bottom processing flow - if we process some kind of data/objects, we often have to chain some calls, receive some data, process it somehow, then pass it somewhere else, etc. In Java we sometimes have to read chained processing left-to-right, sometimes right-to-left or a mix of both. We often have to create several variables for intermediate results or mutate existing variables over and over again with new values. In Kotlin in many cases we can write the code top to bottom, with a simple chaining. This style is supported by: extensions, scope functions, etc.
Scopes/contexts of code - In Kotlin we solve many problems by creating scopes of code: use {}, withLock {}, withContext {}, synchronized {}, etc. Some features which in Java require specific support at the language level, in Kotlin are supported entirely at the library level (synchronized, try-with-resources). Kotlin is a language where we almost can create our own if “statement”, and it will have exactly the same syntax to the original one. This feature is possible thanks to: receivers, inlining, moving last lambda out of parenthesis.
I really like how all these small features come together and provide much bigger features. They just fit nicely into each other.
Honestly, the biggest problem with Java is the community, not the language. Java best practices have evolved in the wrong directions, to the point that it’s a “code smell” if your code isn’t a pain in the ass.
Make getters and setters for no reason. Make a builder class. Use Optional instead of null, and then just pretend that it can’t be null. Use annotations to trigger incomprehensible secret magic. Blech.
Please note all problems with the Java community you mentioned* don’t exist in Kotlin community, because… they were fixed at the language level. So maybe they were in fact problems with the language?
*) All except annotations, but annotations are pretty much the same in both langs.
You can write it easily, but that means nothing to the compiler. To actually benefit from it you need additional tools which are (in my experience) not as reliable as the Kotlin compiler.
And anyway, you have all the nois induced by managing nullable values. The null-safety in Kotlin relies on the fact that nullable values are identified, and the language provides elvis operator to manage them easily.
I’m desperate that in modern java, I’m still forced to do that kind of code:
if (stuff == null || stuff.thing == null || stuff.thing.other == null)
throw new IllegalArgumentException("other thing in stuff should not be null");
Whereas in Kotlin, I can just do:
val value = stuff?.thing?.other
?: throw IllegalArgumentException("other thing in stuff should not be null")
But what I am very puzzled about, is how Java evolves. Since Java 8, I have the impression that java “picks” features from other languages like Kotlin, but … in a worse/degraded form. For example :
java 11 → var : limited to local variables, no synthetic val keyword, you still have to write “final var”
java 16 → record : For what reason I do not understand, they decided that :
they would not follow the “get/set” POJO convention, which was already accepted across the community. Result : any library that does property introspection/binding had to do extra work to support them. And now, you’ve got any code that use plain old POJO with the getX/setX convention on one side, and java records on the other with x()…
Compared to Kotlin data class, they are much more restricted, making them less useful. I mean, they cannot extend abstract class: ok, I understand. But why making them read-only ? That prevent using them for stuff like database entities, json binding, editable form data, etc.
java 17 : “pattern matching”… well, not really, just if (obj instanceof Stuff stuff) { ... }. And in Kotlin, I’m sorry to say that if (obj instanceof Stuff) { } is far better, because, then, Kotlin also auto-cast the object even beyond the condition boundary, meaning that it effectively also works when you do :
require (obj is Stuff);
// here, obj has been auto-casted
Now, with java 20, java advances upon pattern matching, making switch operator even more powerful, and that might be the first language feature that is maybe on par or better than a Kotlin feature (when).
But in Kotlin you can use it today (and you could use it in the past several years).
In my pure Kotlin application I’ve almost never seen NullPointerExceptions, and it’s a huge win.