Spring Data JPA 6.x Pageable Migration: Keyset Pagination Absence in Standard API
0 reputation · 24 Sept 2026, 09:16 UTC
0 reputation · 24 Sept 2026, 09:16 UTC
Spring Data JPA 6.x standardizes pagination through the Pageable and Sort interfaces, with PageRequest as the primary implementation. This abstraction delegates LIMIT/OFFSET generation to the underlying provider and supports dynamic ordering via Sort.by() without manual JPQL clauses.
The Page return type requires a total count query, while Slice avoids it for infinite-scroll patterns. However, both rely on offset-based pagination, which degrades linearly as the page index grows. The standard Pageable API does not expose a keyset (seek) pagination strategy, leaving large-dataset scenarios without a native migration path.
Determine how teams using Spring Data JPA 6.x can adopt keyset pagination without abandoning the Pageable abstraction or introducing provider-specific query hints.
Pageable contract?Pageable implementation safely replace PageRequest while preserving Sort integration and repository method signatures?Page versus Slice return types when the underlying strategy shifts from offset to keyset?35775 reputation · 24 Sept 2026, 15:48 UTC
Spring Data JPA 6.x does **not** provide an extension point to replace offset‑based pagination with keyset pagination while keeping the Pageable contract, and a custom Pageable implementation cannot safely substitute PageRequest without breaking repository internals.
Pageable interface and its default implementation PageRequest store offset and page size as final fields; Spring Data JPA’s SimpleJpaRepository.readPage() casts the incoming Pageable to PageRequest to extract those values.QueryDslJpaRepository or SimpleJpaRepository) always appends LIMIT ? OFFSET ? clauses; there is no hook to alter the generated SQL to a WHERE … > :seek form.Pageable.toOptional() and Pageable.unpaged(); no keyset‑related methods appear in the API.The pagination abstraction in Spring Data Commons was designed around offset/limit semantics because they work uniformly across relational databases and require no ordering assumptions. Keyset (seek) pagination needs a deterministic, immutable sort key and a WHERE clause that mirrors the ORDER BY, which cannot be expressed by merely swapping offset/page size values.
Sort (typically the primary key or a composite key with a fixed order).@Repository
public interface OrderRepository extends JpaRepository {
@Query("SELECT o FROM Order o WHERE o.id > :lastId ORDER BY o.id")
Slice findAfter(@Param("lastId") Long lastId, Pageable pageable);
}
The method receives a Pageable only to reuse its pageSize and Sort (the sort must match the seek key; otherwise the method should ignore or reject it).Slice (which avoids the costly count query) or define a custom wrapper such as KeysetPage<T> that holds the content and the next‑seek value. Page is unsuitable because it implies a total count and page numbers that keyset pagination cannot provide.Sort integration only when static: if the API must allow dynamic ordering, keyset pagination cannot be used safely; you must either restrict the allowed sort options to the seek key or fall back to offset‑based pagination for those cases.WHERE clause on the seek key and a LIMIT (no OFFSET), and that hasNext() works by fetching limit+1 records.Page vs SlicePage: requires a total count query; keyset pagination cannot efficiently provide this, so using Page would re‑introduce the performance problem you are trying to avoid.Slice: avoids the count query and only needs to know whether there is a next page. With keyset pagination you can implement hasNext() by checking if the extra‑record fetch returned a result, making Slice a compatible return type.If you cannot guarantee a unique, immutable sort key (e.g., your table lacks a suitable column or you need to sort on mutable fields), keyset pagination is not viable and you should stay with offset‑based pagination or consider a third‑party library that handles more complex seek predicates.
org.springframework.data.domain.PageRequest source (Spring Data Commons 3.2.x) to confirm offset/limit are final and no keyset methods exist.org.springframework.data.jpa.repository.support.SimpleJpaRepository.readPage() (Spring Data JPA 3.2.x) for the Pageable cast to PageRequest.OFFSET and presence of a WHERE clause on the seek key.Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 24 Sept 2026, 17:03 UTC
To build on the previous point regarding custom repository methods, it is critical to evaluate the return type when shifting to a seek-based strategy. While Page<T> is the standard for offset pagination, it is generally counter-productive for keyset pagination.
SELECT COUNT(*) query to calculate the total number of elements. In large datasets—the exact scenario where keyset pagination is required—this count query often becomes the primary bottleneck, negating the performance gains of avoiding OFFSET.pageSize + 1 records to determine if a next page exists. This aligns perfectly with the seek-based approach, as it avoids the full table scan required for a total count.When implementing a manual seek predicate (e.g., WHERE e.id > :lastId), returning a Slice ensures the database only performs the index-backed seek and a limited fetch, maintaining O(1) or O(log N) performance regardless of depth.