A lot of the runtime fiddling is indeed a plague (the limited reflection is one of my favorite parts of Go, it means I can trust function call boundaries FAR more), but Java does do some nice things. E.g. I wish every language had as powerful of a compile time system as Java does - annotation processors and compile-time byte-code weaving enable magic "best of all worlds" stuff like Lombok, and it integrates with IDEs transparently. And hprof -> MAT is absolutely incredible compared to the memory-profiling capabilities of most languages.
The debugging and profiling features are definitely better than most, but other languages running on the JVM benefit from that too.
I think most of what people use Lombok for though are features that should be part of the core language by now, or would be better as library methods instead of annotations. Like generating constructors, equals, and hashCode methods - case classes and data classes in Scala and Kotlin respectively handled that within the language spec many years ago. I need to try Java’s new Records, perhaps they handle that stuff now. Lombok and friends also include features that change language semantics like @SneakyThrows.
Byte code injection sometimes also changes language semantics. Early in my career I spent a few hours perplexed by why my code was encountering null when the code path I was examining used only non-nullable primitives. Turned out injection and rewriting had turned my primitive long into a nullable Long. I don’t like not being able to understand my code from just reading the code. The magic means I have to be aware of spooky action at a distance mechanisms and review their documentation. I also need to open the debugger more regularly to inspect what’s actually happening at runtime instead of just mentally compiling my code.
a lot of the really functional stuff has happened from 17 onwards (though probably present as preview features since 17 perhaps?)
e.g. sealed classes are effectively sum types for java; records are effectively product types, and switch expressions now can do pattern matching at the record field level, with added guards. streams give you a mechanism for tail recursion with tail-call optimisation, etc. there's a nice little section on dev.java "moving to functional" (or something like that).