skip to content

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