Hibernate reports in-memory pagination warning when using setFirstResult/setMaxResults with join fetch
0 reputation · 02 May 2023, 03:44 UTC
0 reputation · 02 May 2023, 03:44 UTC
When using Hibernate’s setFirstResult() and setMaxResults() to paginate a query that also employs join fetch on a @OneToMany association, the ORM may emit the warning HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!. This indicates that Hibernate cannot push the limit/offset clauses to the database and instead loads the full join result into the JVM for slicing.
The goal is to determine whether the pagination can be kept entirely in SQL without triggering the in‑memory fallback, considering constraints such as the need to avoid duplicate rows, preserve fetch semantics, and maintain compatibility across dialects.
Uncertainty remains around whether adding DISTINCT, adjusting fetch mode, or switching to keyset pagination eliminates the warning while still delivering the intended result set.
34775 reputation · 02 May 2023, 15:39 UTC
No. With a join fetch on a @OneToMany collection, Hibernate cannot safely push setFirstResult/setMaxResults to SQL. The ORM emits HHH000104: firstResult/maxResults specified with collection fetch; applying in memory! and performs pagination in the JVM after loading the full join result.
Confirmed fact: the warning is emitted by Hibernate when a collection is fetched via join and pagination is requested. The generated SQL will not contain a reliable LIMIT/OFFSET for the root entities, and the full Cartesian product is materialized in memory before slicing.
Likely explanation: a SQL LIMIT applied to a join that multiplies parent rows by collection rows would truncate children of a parent and change entity integrity. Hibernate therefore falls back to in-memory pagination to preserve the mapping semantics.
DISTINCT in HQL/JPQL removes duplicate root objects in Java, but does not change the SQL pagination problem. The warning remains and the full result set is still loaded.hibernate.show_sql=true and check logs for HHH000104. Compare execution time for page 1 vs a high offset; linear growth indicates in-memory slicing.@OneToMany in a paginated query. Paginate the root only.// 1. Page the identifiers only
List<Long> ids = session.createQuery("select e.id from Entity e order by e.id", Long.class)
.setFirstResult(offset).setMaxResults(limit).getResultList();
// 2. Load entities with join fetch by the id set
List<Entity> entities = session.createQuery("select distinct e from Entity e join fetch e.children where e.id in :ids", Entity.class)
.setParameter("ids", ids).getResultList();
This keeps LIMIT/OFFSET on the first query and allows a safe join fetch on a bounded id set.@BatchSize on the collection. The root query can then use SQL pagination, and children are loaded in batches in a second round trip.Assumption: the goal is to paginate root entities while still needing the collection initialized. If the collection can be loaded lazily after pagination, the two-step approach can be simplified to batch loading.
One missing diagnostic that changes the recommendation: do you require the @OneToMany collection to be fully initialized in the same transactional query, or is post-load initialization acceptable? The answer determines whether the two-step ID query or a @BatchSize lazy strategy is preferred.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.