Disclaimer: I am one of the maintainers of one such tool, namely https://github.com/uber/NullAway
It certainly won't help with verbosity (in fact, it does the opposite to at least a small degree :)), but it can dramatically reduce the number of actual NPEs when running your Java code.
Obviously I am not saying this is a reason to not use Kotlin. But, when, e.g. based on the things mentioned elsewhere in this comment section, you are deciding to stick with Java for a particular codebase, it doesn't mean static null checking is not an orthogonal option to consider.
I currently find myself playing around now a lot with Dart which has a lot of the same convenience and just handles it via code generation rather than dynamic magic at runtime. For me at least, it's a really nice trade off. It also for the record has fully sound null safety which is something I have only ever seen in Swift previously but is a real pleasure to work with.
I guess in the Java world you have options like Quarkus that also give you a similar kind of experience.
> The cons are mostly compile time and startup time. Startup time isn't very relevant for backend services. Compile times aren't great but not horrible either.
I'd also throw in readability into the mix. Without knowing the magic it's hard to reason about. Then even by knowing what the magic does you have to have some intuition for its arbitrary rules based on its implementation in reflection-land.
<rant> Let's take an example: Autowiring. Java is the type of language heavily that pushes programmers to use the Strategy Design Pattern. It's almost the core of the language's philosophy. However, if I want a file that globally states to always use a specific Strategy then I have to turn away from native Java to the dark magic of dependency injection frameworks (Guice, Spring). If I'm reading such code, I have to know to look for a file that maps a Bean to a specific strategy; which is not apparent anywhere on either file. It's somewhere else in XML or a vague annotation that I have to now wrap my head around. To do so, I have to understand arbitrary limitations to the dark magic like whether this Bean is matched by name, type, or qualifier. All of this can be a little overwhelming at times when you're looking at a file where every variable is a Bean. </rant>
But if you drink the cool-aid, boy does it taste good. I can't start a new project without Guice (pun intended) now-a-days.
In principle we could do the same in Java. The wiring code would contain the word `new` a lot though.
In PHP 5.2 you can't even write inline callback functions, but since then you have closures, type signatures for functions (both for class types and scalar types), namespaces, short array syntax, anonymous classes, generators, null coalesce operator, typed class properties to name a few.
And with PHP 8 new leap forward with things like named arguments, attributes (similar as Java annotations), constructor property promotion, union types, match expressions, nullsafe operator.
Other interesting new stuff is FFI (Foreign Function Interface) so you can all C code directly from PHP.
Performance is also increased a lot, most notably is the array performance much better, opcache built in, but other things like now PHP has a JIT. And GC today is way better than the old days.
PHP community has also changed, lots of good frameworks and libraries and you can use composer (like npm but better IMHO) to use with your project.
You can of course write PHP as the in PHP 5.2 days if you want because new features are optional, so you can take your old code and (almost) run it in PHP 8, but if you adopt modern PHP with strict typing is as good long term as Java, especially if use something like Jetbrains PhpStorm IDE or some of the many static analyzers (psalm, phpstan, phan) that exist today.
https://www.php.net/manual/en/migration56.new-features.php
https://www.php.net/manual/en/migration70.new-features.php
https://www.php.net/manual/en/migration71.new-features.php
https://www.php.net/manual/en/migration72.new-features.php
https://www.php.net/manual/en/migration73.new-features.php
Kotlin/Java with a framework like Spring is the sweet spot for web/api development. Fast enough for most use cases, great tooling, huge ecosystem, just the right level of abstraction, easy to deploy, easy to debug, very expressive code using annotations. An API backed by an ORM can be written in 10 minutes with less than 50 lines of Kotlin.
The cons are mostly compile time and startup time. Startup time isn't very relevant for backend services. Compile times aren't great but not horrible either.
The pain point with Java as mentioned in other comments in this thread is the presence of null, which among some other things make it a little verbose. That's where Kotlin comes in. Usually my projects will use framework libraries in Java and my own code is all Kotlin. The nulls go away once code exits from Java land into Kotlin land.