skip to content

Pageable, Sort & Limit

Pageable and Sort carry paging and ordering into any repository method, with Limit and the Top/First keywords for bounded results. Basic, but interviewers follow up on sorting by an unindexed column and on unbounded queries.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is Pageable in Spring Data, and how do you create and use a PageRequest to fetch one page of results?

level: juniorimportance: must knowfreq 72%

answer

  1. PageRequest.of(page, size) — page is 0-based
  2. Pageable = interface, PageRequest = impl
  3. last method parameter
  4. Page adds count query; Slice does not
  5. Pageable.unpaged() = no limit

basics

~20 s

Pageable describes which slice to fetch — a page number and a page size. You create one with PageRequest.of(page, size) and pass it to a repository method. Page numbers are 0-based, so page 0 is the first page.

solid answer

~40 s

Pageable is an interface that carries paging (and optionally sorting) instructions into a Spring Data repository query. The standard implementation is PageRequest, created via the static factory PageRequest.of(pageNumber, pageSize), where pageNumber is zero-based. Overloads accept a Sort or a Direction plus properties. You pass the Pageable as the last parameter of a repository method: List<User> or, more usefully, Page<User> findByActiveTrue(Pageable pageable). Spring translates it into the store's LIMIT/OFFSET (or equivalent). Returning Page<T> also runs a count query so you get total elements and total pages; Slice<T> avoids the count. Use PageRequest.of(0, 20, Sort.by("createdAt").descending()) to combine paging and sorting in one object. Pageable.unpaged() disables paging. Never hand-roll offset math — let PageRequest and next()/previous() navigate.

code

java · 16 lines
java
public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByActiveTrue(Pageable pageable);
}

// caller
Pageable firstPage = PageRequest.of(0, 20, Sort.by("createdAt").descending());
Page<User> page = userRepository.findByActiveTrue(firstPage);

long total = page.getTotalElements();
boolean more = page.hasNext();
List<User> rows = page.getContent();

// next page without manual offset math
if (page.hasNext()) {
    Page<User> second = userRepository.findByActiveTrue(firstPage.next());
}

go deeper

for a junior

Know PageRequest.of(page, size), that page 0 is first, and pass it as the last method param.

for a middle

Explain Page vs Slice vs List cost, unpaged(), and combining Sort into the PageRequest.

for a senior

Discuss count-query cost, deep-offset performance, native-query countQuery, and 0-based mapping pitfalls at the API boundary.

for a principal

Weigh offset pagination vs keyset/cursor pagination for large datasets and set org-wide list-endpoint conventions (max page size, default sort, count avoidance).

**What problem it solves.** Returning an entire table from a repository method is dangerous — it can load millions of rows into memory. *Pagination* means fetching results in bounded chunks ("pages"). Spring Data models a page request with the `org.springframework.data.domain.Pageable` interface. **Pageable and PageRequest.** `Pageable` is an interface; the concrete implementation you almost always use is `PageRequest`. You never call `new PageRequest(...)` (the constructor is not public in modern versions) — you use the static factory: - `PageRequest.of(int page, int size)` — page is **zero-based** (page 0 is the first page); size is the number of rows per page. - `PageRequest.of(int page, int size, Sort sort)` — page plus a sort order. - `PageRequest.of(int page, int size, Sort.Direction direction, String... properties)` — convenience for a single direction across properties. **How it reaches the query.** You declare the `Pageable` as a method parameter (conventionally the **last** parameter) of a repository method: ```java Page<User> findByActiveTrue(Pageable pageable); ``` Spring Data derives or executes the query and appends the store-specific limiting clause. For JPA/SQL that becomes `LIMIT size OFFSET page*size` (dialect-dependent). The framework computes the offset for you via `pageable.getOffset()` (= `page * size` as a long). **Return types.** The method's return type decides how much extra work happens: - `Page<T>` — the slice **plus** a separate `SELECT count(...)` query, so you get `getTotalElements()` and `getTotalPages()`. - `Slice<T>` — the slice with a `hasNext()` flag but **no** count query (it fetches size+1 rows to know if more exist). - `List<T>` — just the rows, no count, no next flag. **Navigation helpers.** `Pageable` exposes `next()`, `previousOrFirst()`, `first()`, `withPage(n)`, `getPageNumber()`, `getPageSize()`, `getSort()`, `hasPrevious()`. So you rarely do arithmetic yourself. **Unpaged.** `Pageable.unpaged()` returns a special instance meaning "no paging" — the query runs without a limit. `Pageable.unpaged(Sort sort)` (newer versions) applies sorting but no limit. Useful when a method signature requires a `Pageable` but a caller wants everything. **Gotchas.** - The page index is **0-based**, not 1-based — a very common off-by-one bug when mapping from a `?page=1` UI parameter. - `getOffset()` returns a `long`; deep pages produce large offsets and slow OFFSET scans in SQL. - Passing a `Pageable` to a native `@Query` works, but for native queries you often must also supply a matching `countQuery`. - A `Pageable` with no `Sort` gives the database freedom to return rows in **any** order, so page 2 may overlap page 1. Always sort on something deterministic (see the paging-stability question). **When to use.** Any list endpoint that could grow — search results, feeds, admin tables. Prefer `Page` when the UI needs total counts/page numbers; prefer `Slice` for infinite-scroll where a count query is wasteful.

  • Why does returning Page<T> instead of List<T> cost more?
    Page<T> triggers an extra SELECT count(...) query so it can compute getTotalElements()/getTotalPages(). List<T> and Slice<T> skip that count; Slice fetches size+1 rows just to set hasNext().
  • A user asks for '?page=1' meaning the first page. What must you do?
    Subtract 1 — Spring's page index is zero-based, so UI page 1 maps to PageRequest.of(0, size). Forgetting this silently skips the first page of data.

saying these in an interview costs you the question

  • Thinking the page index is 1-based
  • Believing PageRequest.of always runs a count query regardless of return type
  • Computing offset manually instead of using next()/getOffset()
  • Assuming results are ordered even without a Sort

context

open as a page

How do you build multi-field sorting with Sort, Sort.Order and Sort.Direction, including case-insensitive and null-handling options?

level: middleimportance: must knowfreq 58%

basics

~10 s

Use Sort.by(...) to name properties. For fine control, build Sort.Order objects — Order.asc("x"), Order.desc("y") — combine them with Sort.by(order1, order2), and refine each with .ignoreCase() or .nullsLast(). Direction is Sort.Direction.ASC or DESC.

open as a page

How do you bound a result set in Spring Data using the Top/First keywords versus the dynamic Limit parameter?

level: middleimportance: should knowfreq 42%

basics

~20 s

Static bound: put Top or First plus a number in the method name, e.g. findTop10ByOrderByScoreDesc. Dynamic bound: add a Limit parameter — Limit.of(n) — passed at call time. Both cap how many rows come back.

open as a page

Why can offset pagination skip or duplicate rows, and how do Page, Slice, sort stability, and keyset pagination address the trade-offs at scale?

level: principalimportance: should knowfreq 30%

basics

~20 s

Offset paging (page*size) is only correct if the sort order is total and stable — otherwise ties or concurrent inserts shift rows and pages overlap or skip. Add a unique tie-breaker like id. Page runs a costly count; deep offsets scan far. For large data, use keyset pagination.

open as a page

What is Sort.TypedSort and what advantage does it give over Sort.by(String...)?

level: seniorimportance: nice to knowfreq 18%

basics

~10 s

Sort.TypedSort lets you build a Sort from method references instead of String property names, so sort fields are checked by the compiler and survive refactoring/renames. You get it via Sort.sort(EntityClass.class).

open as a page