Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Hibernate’s N+1 problem occurs when an operation runs a query for its root results, then issues additional, similar SELECT statements as it loads related records. Find it by inspecting SQL for the entire operation—not just the initial query—and resolve it by choosing a fetch plan that matches the data the use case actually needs.

What an N+1 SELECT problem looks like

Suppose a query loads a list of orders, then application code accesses each order’s customer. Hibernate may run the root query and follow it with a separate SELECT for each customer. With N root results, that can mean one initial query plus as many as N secondary queries for that association. The exact count depends on the mapping, query, and what the code accesses.

Lazy association traversal is one common cause. N+1 can also occur with an EAGER association: if a JPQL query does not fetch that association, Hibernate may issue a secondary select for each result to satisfy the eager requirement before returning the results. Hibernate documents this behavior in its ORM 7.0 user guide and ORM 7.1 introduction. It is a data-access design problem to diagnose, not by itself evidence that Hibernate is malfunctioning.

How to identify N+1 in your application

  1. Reproduce the real operation. Run the slow endpoint, service method, or batch with representative data. A tiny fixture may not reveal the cost of repeated queries.
  2. Inspect SQL for the whole operation. Include the work after the root query returns, such as mapping entities into a response or traversing associations. The initial query alone cannot show secondary loads triggered later.
  3. Look for repeated query shapes. A common signal is one root SELECT followed by many structurally similar SELECTs keyed by individual IDs or foreign keys. Correlate those statements with the associations accessed on the code path.
  4. Record the context. Note the root query, repeated SQL, association path, result or page size, and the number of to-many paths involved. These details help distinguish an N+1 from an intentional set of queries and inform the choice of remedy.

There is no universal query-count threshold or single diagnostic tool established for every application. Validate the SQL generated by your deployed Hibernate version, database, mappings, and actual data shape.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose a fetch plan for the use case

Start with the data the operation needs, then decide how to load it. Hibernate’s current stable guide recommends LAZY associations with eager fetching selected per query; an EAGER mapping can cause secondary selects when a JPQL query omits the association. Avoid changing many mappings to EAGER just to silence one N+1: unrelated operations may then load data they do not need or trigger additional queries. See the current Hibernate ORM user guide.

Fetch join when the query needs the association

A JPQL or HQL fetch join can load an association with the root query. For example:

select o from Order o left join fetch o.customer

Use left join fetch when orders without a customer must remain in the result. An inner join fetch excludes roots without a matching association. A fetch join is often a good fit for a needed to-one association or a single to-many path, but fewer statements do not automatically mean less work: joined rows can multiply.

Hibernate ORM 7.2 warns that fetching multiple collections or other to-many paths in parallel can create a database Cartesian product and poor performance. It also advises against fetch joins with limited or paged queries and with scrolling or streaming. Check the ORM 7.2 query language guide and verify behavior for your version.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use an entity graph for a dynamic fetch plan

Entity graphs let a query or operation specify the associations to load without encoding every use case into static mappings. Hibernate distinguishes fetch-graph and load-graph behavior, and its stable user guide describes graphs as a load plan. The relevant APIs and hint names depend on your Hibernate and Jakarta Persistence versions; do not copy an older example using legacy javax.persistence names into a newer stack without checking compatibility.

Consider batch or subselect fetching for lazy associations

Batch fetching loads several associated records in a secondary query using a group of keys. Subselect fetching can load associations for owners returned by an earlier query. Both can reduce repeated round trips while preserving lazy access, and may be useful when joining multiple collections would create an unwieldy result set.

These strategies mitigate some N+1 patterns, but they are not a universal replacement for a deliberate fetch plan. Hibernate’s ORM 7.1 introduction explicitly cautions that batch fetching may mitigate N+1 problems without solving them in general. Choose a batch size for the application’s data and workload; the documentation cited here does not establish one universally optimal value.

Use a DTO or projection for a narrow read

If a response needs only selected fields rather than managed entities and their associations, a DTO or projection query may return the required read model without loading a large object graph. Hibernate ORM 6.1 documentation describes DTO projection or a fetch join as often preferable to relying on @BatchSize when one query can retrieve the needed data. Compare selected columns and returned rows as well as statement count, and favor the shape that best fits the caller. See the ORM 6.1 user guide.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Compare the trade-offs before changing code

Approach Best fit Watch for
Fetch join Association needed immediately, especially a to-one or one to-many path Row multiplication with parallel to-many paths; limitations with pagination, limits, scrolling, and streaming
Entity graph A fetch plan that varies by use case Version-specific API and fetch/load graph semantics
Batch fetching Lazy associations accessed across a group of owners Mitigates repeated loads but does not necessarily remove the underlying N+1 pattern; size must be validated
Subselect fetching Associations of owners returned by an earlier query May be preferable to a large join in some cases, but query shape and workload still matter
DTO or projection A read operation needing only selected fields Ensure the projection remains maintainable and returns the right columns and row shape

Assess each option on statement count, rows and bytes returned, association cardinality, pagination needs, and the data the caller uses. The strategy that minimizes SQL statements may still be slower if it transfers a greatly expanded result.

Verify the fix against the deployed stack

After changing a fetch plan, rerun the same operation with representative data and inspect the full SQL sequence again. Confirm both that repeated per-root selects have been removed or bounded as intended and that the replacement query has not created excessive duplicate rows or fetched unrelated data. Hibernate guidance spans multiple ORM releases, so confirm generated SQL and API behavior on the version and database your application actually runs.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.