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?

5 Likes

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.

6 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.

2 Likes

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!

3 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.

5 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).

2 Likes

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.

3 Likes

Whereas in Kotlin, I can just do:

val value = stuff?.thing?.other
    ?: throw IllegalArgumentException("other thing in stuff should not be null")

That might be even shorter:

val value = requireNotNull(stuff?.thing?.other)

The mentioned limitations of records are there for a reason. Immutability helps to prevent bugs and fits the idea of a pure data structure very well. Personally I’m very happy that they also abandoned the evil getter/setter pattern.

In the case of pattern matching I see Java ahead of Kotlin, since Java allows deconstruction of deeply nested object structures. Java is basically in the same league as Scala now, even though the Scala syntax looks better.

1 Like

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

Maybe I misunderstand what you mean, but I don’t think that’s correct. The Java compiler checks that a final variable is set in all branches, e.g.

final int x;
if (1 == 0) x = 1;
else x = 2;
System.out.println(x);

I didn’t say the compiler doesn’t check this. I said this is a code smell to me. It makes the code much harder to reason about when reading it.

We can recreate your above example in Kotlin, but then IntelliJ shows a warning about the code style and suggests to fix it. Even before using Kotlin, I tried to avoid this pattern in Java by extracting the if-else contents to a separate method. But well, this is still a rather normal pattern in Java, due to limitations of the language.

1 Like

I see. Since the context was mutability, I thought you thought Java needed a mutable local variable for this case. :slight_smile:

Regarding the pattern – I think I find

val foo: Int
if (1 == 0) {
    // some calculation...
    foo = 1
}
else {
    // some calculation...
    foo = 2
}

just as clear and maintainable as

val foo = 
if (1 == 0) {
    // some calculation...
    1
}
else {
    // some calculation...
    2
}

In some cases, I’d probably prefer the first solution. But that’s a matter of taste, of course.

The above I have to disagree with.

  • Functions as first class citizens: Functions in java have always been first class citizens. Since Java 8 you can get method references in the same manner as kotlin. IE object::methodName. Prior to that you can use the reflection API.
  • if as expression, especially compared to condition ? result1 : result2 : I am on the opposite side of this divide. I miss the tertiary operator. I hate that if can return an expression. It’s a source of errors in my opinion and one of the few mistakes of the language designers because it increases verbosity and is a feature that no other language has, so causes confusion and errors. I also think that excluding the tertiary operator doesn’t make sense in light of similar functionality with the elvis operator on null objects. IE saying object?.value ?: defaultValue is actually just object != null ? object.value : defaultValue. If you like one, there is no reason not to like the other. Wish they would have gone with the ?? as the nullable check instead of ?: then maybe they would have kept the tertiary operator.
  • lambda expression outside of the parameter list : Since this only works on functions with only one lambda parameter and not multiple lambda parameters, then I also think this was a mistake of the designers as it can lead to some truly ugly code with dangling lambdas out in space that just look like random code blocks for no reason.

Functions in java have always been first class citizens.

I disagree. For a very long time you’d need to write an anonymous class to have something like a “function”. Maybe some of you remember this pattern from Swing or Wicket:

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        doSomething();
    }
});

What is available today in Java with the so called “lambda expressions” (what is actually a specialization of “function”) is much better, but is still not so nicely integrated into the language as in Kotlin. For example, an explicit interface is needed for functional types. The following wouldn’t be possible in Java without an explicit interface (Supplier in this case).

fun <Result> doInTransaction(block: () -> Result): Result

That it was possible to get method references with the help of reflection prior Java 8 is not exactly what I would call “first class citizen” :wink:

The other points you mentioned are controversial, but I can see your point.

1 Like