What I like about Java is that it is a deliberate late-mover language. It doesn't go for the latest shiny thing but looks at what features have proven themselves in other languages.
I can't help but to be a little sobered by the promises of Kotlin and C#. They look great on the surface, but once you have more complicated code to work on they tend to show warts quite fast.
Java is remarkably consistent and really doesn't have any sharp edges. Even with generics and type erasure, I found myself fighting the language semantics in C# way more often than Java.
I personally enjoy Kotlin. There are potential warts with it, but those exist with other languages.
That said, Java taking it's time and observing the playing field is a huge benefit. And now that they've been slowly delivering new language features, it's really made it pretty nice flow of powerful well thought out extensions each release. There hasn't been one of the new features that I think missed the mark.
> A sharp edge is writing meta typesystem code infolving covariance, invariance etc
Like in Java? C# has its own sharp edges (like most or all languages), but it's almost disingenuous to bring a feature that is much more complex and weird in Java as an example.
Java lacks a ton of quality-of-life features that are table stakes in other languages, including C# and Kotlin. Auto-properties, async, extension methods, nullable reference types, primitive types, string interpolation/templates -- Java is inferior to C# in every one of these areas.
Java's approach to async is virtual threads. Extension methods are being explored through typeclasses. Solutions for nullability are also proposed, and in the mean time, you can use JSpecify. If by primitive types you mean value classes, they are going into preview in the upcoming JDK.
I can't help but to be a little sobered by the promises of Kotlin and C#. They look great on the surface, but once you have more complicated code to work on they tend to show warts quite fast.
Java is remarkably consistent and really doesn't have any sharp edges. Even with generics and type erasure, I found myself fighting the language semantics in C# way more often than Java.