Kotlin vs. modern Java

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?

1 Like

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)
3 Likes

Some points for Kotlin:

  • Properties
  • Read-only collections
  • Receivers
  • Scope functions
  • Flows
  • Automatic it name.
  • 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.

4 Likes

Coroutines and virtual threads are different, they resolve different use cases.

There is a plan to introduce “static” in Kotlin to define “namespaces” :confused:

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.

1 Like

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.

2 Likes

I personally appreciate

  • Extension functions and properties
  • Nullable types with ?: ?.
  • == uses equals() and >, <, <=, >= can be used with Comparables
  • Default arguments
  • lateinit fields
1 Like

Null-safety alone makes Kotlin more practical IMHO.

And don’t forget multiplatform development!

2 Likes

you can use @NotNull in Java easily.

Context receivers are also game-changers when designing readable APIs.

you can use @NotNull in Java easily.

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.

4 Likes

you can use @NotNull in Java easily.

You can write it easily

… and it often adds lots of “noise” to the code to make it less readable and more verbose.

1 Like

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 :
    1. 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()
    2. 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).

1 Like

Sooner or later there will be null-safety in Java as well, see e.g. Design document on nullability and value types

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.