The HN crowd is not very fond of the magic in popular Java frameworks but to me that magic is what makes those frameworks so good, although with a bit of a learning curve at first.
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.
While admittedly not as nice as having it built into the type system from day one, there are a number of tools one can integrate into their build to add various degrees of null safety to Java.
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'd be totally OK with the magic in something like Spring or Ruby on Rails is another good example if it had code I could inspect when things go wrong. That is really my one and only complaint that I have about "magic".
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.
I aspire to be like the writer of this comment. Using the good parts of both languages to be a productivity machine.
> 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.
I've written a lot of server-side Kotlin recently. No dependency-injection framework. Our `main` method does the wiring up explicitly in code. No magic. The wiring up code is very straightforward and not very long. I would not go back.
In principle we could do the same in Java. The wiring code would contain the word `new` a lot though.
There is tooling for that now-a-days. IntelliJ shows a gutter icon on both the declaration and use side so you can click your way to the bean definition. Just like you can with implementations of an interface. It’s even profile/configuration aware.
Gosh... At my former employer we used Java/Spring for web backends. I am now using PHP/Laravel, it feels like another world when it comes to productivity.
10 years ago we are at PHP 5.2 days. PHP has evolved a lot since then, back then it was cumbersome, but things started to change with PHP 5.6 release and then with the entire PHP 7.x releases (skipped PHP 6) a new standard was set of language improvements, performance improvements and clean up of old bad behavior, impressive work from the core team.
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.
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.