There are lots of valid reasons to avoid JPA/Hibernate beyond those listed in the article.
- Hibernate is using blocking IO and threaded database pools. This makes it a problem if you want to use non blocking web frameworks. Like Spring's web flux. You should consider it a legacy technology for this reason alone. There's a good reason why there is no drop in reactive replacement: modern frameworks are trying to not repeat some of the design problems with hibernate (see the article for an overview of those).
- It comes with its own category of hard to diagnose and fix bugs. I've been on more than one project where I had to clean up other people's messy transactional logic. One symptom is people copy pasting @Transactional everywhere as if it was some kind of magical incantation that says "dear db gods please just make this work consistently". A second symbol is flaky tests where that clearly is not working as advertised. A lot of this relates to things like aspect oriented programming and reflection which are what hibernate uses to generate byte code at run time.
- For the same reason, hibernate is also a problem if you want to natively compile your code via e.g. Graal. Reflection and byte code generation are problematic for that. The less you have of that, the better.
- For the same reason, hibernate is also inappropriate if you need fast startup times (which would be why you'd consider native compilation). For example because you are doing server-less designs or edge computing. Having 10-15 seconds of startup overhead is not great. Warming strategies can mitigate this somewhat. But honestly, there's no good reason for Java servers to take much longer to start than it takes the JVM to start (which is still around a second or so). As soon as you get rid of hibernate, Spring Boot startup times become a lot more reasonable. If you then switch to using it's bean DSL (as opposed to scanning packages for annotations), it gets better still. Most of what Spring does at startup is millions (literally) of calls into various reflective methods. That's why it takes so long.
- Object impedance mismatch. Designs that need an ORM layer might not be that optimal. I've been on multiple teams where people got carried away a little too much with e.g. overusing inheritance and coming up with complex solutions to make the database mirror the class hierarchy. The result is dozens of tables and dozens of joins on read. I've seen GET operations that had 1500ms response times because of this. It's stupid. It's stupid even after you fix all the silly joins, missing database indices, etc. You can do good database design with hibernate of course. If you understand how to do that, hibernate is just another tool and not a particularly critical or important one. I've removed it on a few projects to simplify the design.
- These days it is valid to treat databases as document stores. Once you refactor a 15 table database to be the 3 tables that you really needed all along, most of Hibernate is just not needed. My golden rule is that if I don't query on it, I don't need (or want) separate tables or columns for it. Nothing wrong with storing some json blobs. I love using databases because they are fast, transactional, and come with some strong consistency guarantees. Hibernate is not a great fit for document databases. It assumes your domain consists of columns and tables and it wants to do clever things with joins to make that seem like an object tree. The best join is the one you don't need. That's why document stores can be so nice.
If I had to do a green field project, I'd probably go for R2DBC with Spring or maybe one of several other Kotlin reactive database frameworks in combination with ktor, http4k or one of the other emerging Kotlin server frameworks. All my recent projects are using spring web flux and Kotlin co-routines in any case. So, using something non blocking is a hard requirement for me.
But if I had to use hibernate, using Kotlin is the way to do it. It shovels most of the ugliness under the carpet via compiler plugins. So you can use nice immutable data classes and let the kotlin compiler worry about adding default constructors, opening the class and adding getter and setter cruft just so hibernate can do its runtime magic. Also it removes all of the need for hacky things like Lombok and its gazillions of additional annotations. Hibernate can be a lot less painful if you just do it properly. But not using it is better still.