Hibernate: Ditch or Double Down? When ORM Isn't Enough

 

Transcript:

Some developers treat Hibernate as heavens. Others blame it for nasty performance problems. Should you ditch Hibernate or double down? Let’s find out. We will explore where Hibernate shines, where plain JDBC or jOOQ are better, and how to take the best of both worlds in one project.

Hibernate is an object-relational mapping framework. In other words, it follows the ORM approach. This approach means that instead of manually writing SQL queries or mapping database rows to application fields, you just manipulate objects, and the ORM tool usually translates that into database operations. So, Hibernate acts as a translator between Java objects and database tables and relations. It handles the SQL and mapping for you.

Hibernate sits on top of the Java Database Connectivity API, or JDBC for short. It provides data access from the Java programming language, so Hibernate calls JDBC under the hood. But as Hibernate is an ORM tool, it implements the Java Persistence API, or JPA. JPA is a standard specification. Basically, it’s a set of interfaces and annotations that define how an ORM tool must work. Hibernate provides an implementation of JPA-defined APIs such as the EntityManagerFactory, EntityManager interfaces, mapping annotations, and so on.

Plus, it includes some additional features that are part of the native Hibernate API. These include, among others, the SessionFactory, which extends the EntityManagerFactory, and the Session, which extends the EntityManager. In addition, it provides extra mapping annotations that can be used with JPA APIs or the native API. Hibernate also supports multiple SQL dialects. These are classes that translate Hibernate Query Language into the native SQL dialect of the target database. Hibernate supports PostgreSQL, Oracle, MySQL dialects, and many others.

You write your domain models, and then you use Hibernate APIs, Hibernate Query Language, or JPQL to perform CRUD operations. Hibernate generates SQL, or you can write native queries, and it talks to the database using JDBC under the hood. It uses JDBC to open connections, execute the generated SQL, and fetch the results.

Hibernate can accelerate development and boost developer productivity. There is no need to bury yourself in writing absolutely all SQL queries. Most—like 95%—of Hibernate queries work just fine, and the others you can tune manually. Plus, there’s no need to waste time on manual mapping of standard relations such as one-to-many, many-to-many, and so on. It is a piece of cake for Hibernate. You can work with different relational databases with minimal changes.

Hibernate is also deeply integrated into major frameworks such as Spring, Quarkus, or Micronaut. Plus, complex tasks such as caching, auditing, and validating are already solved in Hibernate. For example, let’s take caching. Hibernate’s caching feature stores frequently accessed data in memory, reducing the number of database round trips. The application looks into the cache before hitting the database, which improves performance.

Hibernate provides two levels of caching: first-level cache and second-level cache. The first-level cache stores entities that are frequently used during a single Hibernate session and is enabled by default. The second-level cache stores data frequently used across different sessions. It reduces the number of database queries, but it must be enabled explicitly.

So, what are good use cases for Hibernate? For instance, when you implement domain-driven design with domain models that are based on standard relations such as many-to-one, one-to-many, many-to-many, and so on, or when you need to do specific operations such as locking, as Lukas Eder pointed out in his article comparing Hibernate to jOOQ. Also, your project may need to run on different databases, and you don’t want to litter your codebase with database-specific SQL. In exchange, you are ready to deal with issues related to badly implemented lazy loading, cache, equals/hashCode, and N+1 issues.

But there are some situations when you might want to say goodbye to Hibernate. The tool has several drawbacks. First of all, it is an abstraction and a so-called leaky abstraction. This means that it might lead to performance issues when everything was working fine, and then one day you hit an N+1 issue that has been sitting in your code because of some badly written logic.

The N+1 issue arises when you have an initial query that fetches a number of records from the database, and then another query is executed for each record to fetch data on related rows. For example, we have suppliers and products. We fetch all suppliers and then iterate over them to fetch all their products to retrieve the name of each product. This logic may result in a new query for each supplier. Each query has to be sent to the database, executed, and the results sent back to the application, which may take a lot of time and resources.

Another drawback of Hibernate comes from its power. Hibernate is a very complex tool with powerful features, but misuse of features such as cache or lazy loading may lead to performance issues. Also, writing equals, hashCode, and toString methods with Hibernate is not trivial. You need to ensure the consistency of equals and hashCode and make sure that your toString method doesn’t trigger lazy loading on all fields. In the description, links are attached to articles by Vlad Mihalcea and Thorben Janssen that explore how to better implement these methods with Hibernate.

So, what are the use cases that are better suited for a SQL-first approach? You may have complex read queries that implement window functions, complex joins, and database-specific features. Hibernate struggles with that. Your code might be more data-oriented than object-oriented, which is also a call for plain SQL. You may also have complex data models, for example referencing a part of a composite key, or cases where it is impossible to map JSON to an actual data structure in Hibernate.

On the screen, you can see an example of a query that Hibernate might choke on. Here we have a window function that looks at all rows in the result set, sorts them by the expression given in the ORDER BY clause, and assigns a rank number based on the order. We also have an anti-join, which only keeps rows that do not have a match in some other set.

In this case, we include suppliers but only count non-recalled products. As a result, we rank suppliers by revenue from products that weren’t recalled. Imagine we want to generate a report where we count supplier revenue, rank suppliers by that revenue, and calculate the average price for each supplier. The query shown is better written in plain SQL, and maybe you shouldn’t trust Hibernate to generate such a query.

However, the question of using ORM or SQL has a more surprising answer than you might think, because you can use both in one project. Hibernate and JPA were designed to work together with handwritten SQL, not to replace it completely. Even the creator of Hibernate states that you don’t have to use it everywhere. ORM and JDBC solve different problems, which means you can use both approaches in one codebase.

There’s a great book by Vlad Mihalcea that describes how to do that. Essentially, you can use Hibernate for CRUD operations and plain SQL such as JDBC or jOOQ for cases where ORM struggles. I hope you found this useful. Drop a comment on whether you are totally satisfied with Hibernate or would like to say goodbye to it. And as usual, don’t forget to like, subscribe, and until next time.

Summary

In this video, Hibernate is presented as an ORM framework that simplifies database access by mapping Java objects to relational tables. It improves developer productivity but can cause performance issues due to its complexity and leaky abstractions. For complex or database-specific queries, a SQL-first approach with JDBC or jOOQ is more suitable. The best practice is often to combine Hibernate for standard CRUD operations with plain SQL where needed.

About Catherine

Java developer passionate about Spring Boot. Writer. Developer Advocate at BellSoft

Social Media

Videos
card image
Dec 30, 2025
Java in 2025: LTS Release, AI on JVM, Framework Modernization

Java in 2025 isn't about headline features, it's about how production systems changed under the hood. While release notes focus on individual JEPs, the real story is how the platform, frameworks, and tooling evolved to improve stability, performance, and long-term maintainability. In this video, we look at Java from a production perspective. What does Java 25 LTS mean for teams planning to upgrade? How are memory efficiency, startup time, and observability getting better? Why do changes like Scoped Values and AOT optimizations matter beyond benchmarks? We also cover the broader ecosystem: Spring Boot 4 and Framework 7, AI on the JVM with Spring AI and LangChain4j, Kotlin's growing role in backend systems, and tooling updates that make upgrades easier. Finally, we touch on container hardening and why runtime and supply-chain decisions matter just as much as language features.

Videos
card image
Dec 24, 2025
I Solved Advent of Code 2025 in Kotlin: Here's How It Went

Every year, Advent of Code spawns thousands of solutions — but few engineers step back to see the bigger picture. This is a complete walkthrough of all 12 days from 2025, focused on engineering patterns rather than puzzle statements. We cover scalable techniques: interval math without brute force, dynamic programming, graph algorithms (JGraphT), geometry with Java AWT Polygon, and optimization problems that need constraint solvers like ojAlgo. You'll see how Java and Kotlin handle real constraints, how visualizations validate assumptions, and when to reach for libraries instead of writing everything from scratch. If you love puzzles, programming—or both—and maybe want to learn how to solve them on the JVM, this is for you.

Further watching

Videos
card image
Jan 29, 2026
JDBC Connection Pools in Microservices. Why They Break Down (and What to Do Instead)

In this livestream, Catherine is joined by Rogerio Robetti, the founder of Open J Proxy, to discuss why traditional JDBC connection pools break down when teams migrate to microservices, and what is a more efficient and reliable approach to organizing database access with microservice architecture.

Videos
card image
Jan 27, 2026
Sizing JDBC Connection Pools for Real Production Load

Many production outages start with connection pool exhaustion. Your app waits seconds for connections while queries take milliseconds; yet, most teams run default settings that collapse under load. This video shows how to configure connection pools that survive real production traffic: sizing based on database limits and thread counts, setting timeouts that prevent cascading failures, and implementing an open source database proxy Open J Proxy for centralized connection management with virtual connection handles, client-side load balancing, and slow query segregation. For senior Java developers, DevOps engineers, and architects who need database performance that holds under pressure.

Videos
card image
Jan 20, 2026
JDBC vs ORM vs jOOQ: Choose the Right Java Database Tool

Still unsure what is the difference between JPA, Hibernate, JDBC, or jOOQ and when to use which? This video clarifies the entire Java database access stack with real, production-oriented examples. We start at the foundation, which is JDBC, a low-level API every other tool eventually relies on for database communication. Then, we go through the ORM concept, JPA as a specification of ORM, Hibernate as the implementation and extension of JPA, and Blaze Persistence as a powerful upgrade to JPA Criteria API. From there, we take a different path with jOOQ: a database-first, SQL-centric approach that provides type-safe queries and catches many SQL errors at compile time instead of runtime. You’ll see when raw JDBC makes sense for small, focused services, when Hibernate fits CRUD-heavy domains, and when jOOQ excels at complex reporting and analytics. We discuss real performance pitfalls such as N+1 queries and lazy loading, and show practical combination strategies like “JPA for CRUD, jOOQ for reports.” The goal is to equip you with clarity so that you can make informed architectural decisions based on domain complexity, query patterns, and long-term maintainability.