Inconsistent page size when using Hibernate fetch join with setMaxResults
0 reputation · 19 Jul 2025, 14:24 UTC
0 reputation · 19 Jul 2025, 14:24 UTC
When a Hibernate query uses a fetch join (e.g., left join fetch) together with pagination via setFirstResult and setMaxResults, the application expects each page to contain a exact number of distinct root entities.
However, the LIMIT/OFFSET clause is applied to the joined result set before Hibernate removes duplicate rows, so the number of distinct entities returned can be larger or smaller than the requested page size. This raises the question of whether pagination limits should be applied to the raw joined rows or to the deduplicated entity set, and what configuration or behavior change would achieve consistent paging without losing the benefits of fetch joins.
28775 reputation · 19 Jul 2025, 14:58 UTC
When you use LEFT JOIN FETCH on a collection and add setFirstResult()/setMaxResults(), Hibernate applies the limit to the *raw SQL result set* before it removes duplicate root entities. The database returns a Cartesian product, the limit cuts that product, and then Hibernate deduplicates. The net effect is that the number of distinct root entities in the returned list can be more or less than the requested page size.
Hibernate’s design is to paginate before deduplication. It cannot translate a collection fetch into a database‑level limit that preserves the root‑entity count because the SQL would need to aggregate the collection rows, which JPQL/HQL doesn’t support. The framework therefore performs in‑memory pagination when a collection fetch is present.
No. Hibernate has no configuration flag to change this behaviour. The only way to get a consistent page size is to avoid applying setMaxResults while a collection fetch is in the query. Typical solutions are:
SELECT DISTINCT on the root entity and then fetch collections in a second step (e.g., JOIN FETCH on a separate query).@BatchSize or @Fetch(FetchMode.SUBSELECT) to batch load collections after pagination.If you remove the fetch join from the paged query, the database will apply the limit correctly and you’ll get exactly maxResults root entities per page. However, you lose the eager loading benefit and may incur additional round‑trips when accessing collections. If you keep the fetch join, you must be prepared for in‑memory pagination and potentially large result sets, which can lead to OutOfMemoryError and degraded performance.
WARN org.hibernate.loader.criteria.CriteriaQuerySpecification - firstResult/maxResults specified with collection fetch; applying in memory!
This warning confirms that Hibernate is falling back to in‑memory pagination. If you see it, consider refactoring the query as described above.
Does your query join multiple collections with FETCH? If so, you’ll also hit Hibernate’s MultipleBagFetchException, which forces you to split the fetches anyway.
FETCH on collections and verify that the SQL contains LIMIT/OFFSET and that the result size equals maxResults.SELECT c FROM Child c WHERE c.parent IN :parents, using IN (:parents) with the list of root entities.@BatchSize or @Fetch(FetchMode.SUBSELECT).By separating pagination from eager loading, you maintain predictable page sizes while still benefiting from efficient collection loading.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.