The nice thing about null-safety in Kotlin is that it is built-in and it requires no extra annotations or added tooling. Not having @ProxySingletonAbstractJunkFactoryFactoryBean sprinkled all over your code does it ever so slightly more readable.

But if built-in null safety and lower verbosity was all that Kotlin had to offer I doubt it would have won. Kotlin offers a lot more features that Java does and most probably will not offer in the next 10 years:

- Extension methods: No more terrible FooUtil or BarHelper classes.

- Context parameters (preview feature)

- Coroutines (they are not just about concurrency - you can use them to write generators as well[1]).

- Keyword arguments: This makes using constructing data classes possible without generating builders, and in general lets you deal with multi-argument methods in a safer and more readable way. It also cuts down the boilerplate of defining 20 different overloads that call each other.

- Interface Implementation by delegation: This feature makes it easy and painless to follow the motto "composition over inheritance". Java makes implementation inheritance 50 orders of magnitude easier than composition with manual delegation, so it's pretty natural that the vast majority of Java code- bases overuse inheritance in very bad ways.

- Properties: This reduces the need to worry about direct field access causing API breakage in the future, and removes the need for the absolutely massive boilerplate associated with getters and setters in Java. It's true that records remove the need for that, but records only work for purely immutable data classes (and right now they are only practically usable with a small number of fields).

- Delegated Properties: Not something I use every day, but it's very useful when I do use it.

- Unsigned integers (specifically bytes): Writing any byte-wrangling code in Java is a downright nightmare.

- Reified Generics: Java may get something equivalent to that with Project Valhalla, but I'm not sure how comprehensive it would be.

- If expression: Java made switch an expression with JEP 361, but if is still a statement. This leads to a lot of bloated and somewhat unsafe code where you have to define a variable before the if and then assign to he variable in each branch.

- Expression Bodies for function: This is a very small feature, but it's pretty nice to cut down boilerplate and make functions more readable when you need it.

DSL Features
------------

Kotlin is perfect for DSLs thanks to features like:

- Closure blocks: Kotlin lets you pass the last closure argument to a function as a block of code inside curly braces that follows the function call. This features lends itself very well to readable DSL. In Java you would need to embed the closure inside the function argument list and add an extra parentheses. This gives you some of (see the next couple of points) the DSL capabilities of Kotlin, but the DSL becomes very hard to read and use when a lot of blocks are involved. There is a good reason why Kotlin DSLs are based on blocks, while Java DSLs are based on the less-flexible fluent interface pattern: block-based DSLs are just too hard to read and write in Java.

- Inline functions: Inline functions allow the compiler to perform some optimizations in advance that may be harder for the JIT to do (namely inlining the provided block closures), but the real kicker is that you can use flow control statements (break, continue and return) that affect the calling scope.

- Multiple "this" in context. You can have multiple values for this inside a nested scope. The compiler manages to find the right reference for the "this" alias based on your usage, but in case of ambiguity (where the deepest "this" wins), you can disambiguate your choice. This feature sounds overly complex and unnecessary at first, but it allows a lot of power for the Kotlin DSL system.

- Closures with this receivers: Using the multiple this values from above, you can have closures that receive their own "this" to introduce DSL methods. Being able to introduce these methods without shadowing the "this" from the parent context is crucial for powerful DSLs.

- Infix functions and operator overloading: Needless to say, these features make DSLs even nicer.

I've probably skipped a couple of features I can't recall right now, but I hope that it shows that Kotlin is a lot more than just "Java with null-safety and data classes" as some people think.

[1] https://kotlinlang.org/docs/sequences.html#from-chunks