A Slice reports whether another window exists without the total-count work a Page may require.
Spring Data JPA Page versus Slice: pay for totals only when needed
Match the return type to the screen
An activity feed needs 47 receipts and a next button. It does not display an exact page count. Returning Page asks the repository infrastructure for total metadata and can issue a count query; returning Slice fetches enough rows to determine whether another slice exists. That saves count work, but it does not make the data query free. Use a deliberate count query when an admin report truly needs an exact total.
Use a deterministic order
Ordering only by createdAt is unstable when two receipts share a timestamp. Add id as a tie-breaker, and keep both columns in the query's order. With offset pagination, a new row inserted between requests can still shift later results. For deep scrolling or strong continuation semantics, use a keyset cursor with an index matching the filter and order.
Prove the query budget
Request the first and second slices from a dataset larger than one page; assert hasNext is accurate and IDs do not repeat in the unchanged dataset. Record the SQL. Then request a large offset to observe the database work, and cap page size with input bounds. A Slice is a count-cost choice, not a cure for deep offset scans.
Implementation contract
interface ReceiptFeedRepository extends JpaRepository<ReceiptEntity, UUID> {
Slice<ReceiptEntity> findByTenantId(
UUID tenantId, Pageable page);
}
Pageable firstWindow = PageRequest.of(0, 47,
Sort.by(Sort.Order.desc("createdAt"),
Sort.Order.desc("id")));Cost and verification
A Slice typically reads one extra row to determine hasNext. A Page can require a separate count query, especially expensive with joins. Both offset forms may scan or skip increasing work as the requested page number grows.
Common Mistakes
- Do not request Page totals for a screen that never displays them.
- Do not sort on a non-unique timestamp alone.
- Do not describe Slice as keyset pagination; it still uses an offset with Pageable.
Read next
Spring Data JPA count query for a joined Page, Spring Data keyset pagination: continue after the last stable identifier, Spring API pagination: bounded requests and immutable snapshots, Spring Data JPA Specifications with a mandatory tenant predicate, Spring Data JPA entity graph for a bounded to-one read.
Related data contract
JPA collection batch fetching: reduce N+1 without a giant join.
