Behavior limits of setFirstResult() with collection fetch joins
0 reputation · 01 Sept 2026, 20:14 UTC
0 reputation · 01 Sept 2026, 20:14 UTC
Hibernate ORM exposes setFirstResult() and setMaxResults() on Query and TypedQuery to paginate results, delegating to the configured dialect to emit LIMIT/OFFSET or ROWNUM clauses. When a query fetches a collection association, the framework may log HHH000104: firstResult/maxResults specified with collection fetch; applying in memory! and shift pagination from the database into the persistence context.
This behavior raises a practical boundary question for applications managing large result sets: the documented API suggests SQL-level paging, yet the runtime path may load full result graphs before trimming. The decision matters most when combining fetch joins with high offsets, because the database scan cost and the in-memory filtering cost stack rather than replace each other.
Is the in-memory fallback determined exclusively by the presence of a collection fetch join, or do dialect capabilities and association mapping style change the outcome? How does ScrollableResults interact with setMaxResults() when the underlying query triggers HHH000104, and does it reduce persistence context growth compared to getResultList()? Are there documented cases where a dialect silently ignores setFirstResult() instead of translating it, requiring explicit verification of generated SQL?
35275 reputation · 02 Sept 2026, 04:36 UTC
No. A collection fetch join is the most common trigger, but the in-memory fallback is not determined exclusively by it. The deciding factor is whether the SQL result set duplicates parent rows for each child row. When that happens, LIMIT/OFFSET semantics are ambiguous, so Hibernate applies pagination after loading the full graph. Dialects with native LIMIT/OFFSET support still fall back in this shape because the issue is result-set duplication, not SQL syntax.
Likely explanation: Hibernate detects collection fetch joins such as JOIN FETCH orders and logs HHH000104, then trims setFirstResult() and setMaxResults() in the persistence context. Confirmed behavior assumes Hibernate ORM 5.x or 6.x with a standard relational dialect: native dialect pagination does not prevent the fallback when collection duplication would occur.
Association mapping style can change the outcome. Fetching a single-valued association usually does not duplicate the root row, so database-level pagination may remain intact. Fetching a collection, especially a bag or list without a dedicated primary key, produces the Cartesian duplication that forces in-memory handling.
When HHH000104 is active, ScrollableResults still iterates over the full result set loaded from the database; setMaxResults() does not reduce the rows fetched. It can limit persistence-context growth compared with getResultList() only if you explicitly evict or clear each processed entity, for example entityManager.clear() or session.evict(entity) inside the scroll loop. Without explicit eviction, managed objects accumulate and memory usage is similar to holding the complete list.
Some dialect and query combinations can omit OFFSET or LIMIT from generated SQL, particularly with DISTINCT, GROUP BY, or older ROWNUM-based translations. Hibernate may then apply pagination in memory under a different warning. Because this varies by Hibernate version, dialect, and database release, treat the generated SQL as authoritative.
hibernate.show_sql=true, or attach a StatementInspector.LIMIT, OFFSET, or the dialect equivalent.Missing diagnostic detail: which Hibernate version and dialect are you using? That determines whether a specific dialect silently drops OFFSET and which replacement strategy is safe.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.