A list endpoint that returns everything breaks at scale, so it pages. Spring Data’s Pageable makes paging easy, but the details — a stable sort and how deep pages are fetched — decide whether the results are correct and fast.
Before you start
You should be comfortable with Spring Data repositories and REST controllers. This article covers paging and its pitfalls.
Step-by-step walkthrough
Step 1: Accept and bind Pageable
A controller method can take a Pageable parameter that Spring binds from page, size and sort query parameters. Return a Page or a stable DTO so the client gets the content plus the total and page metadata.
Step 2: Always apply a stable sort
Without a deterministic sort, rows with equal values can shift between pages, so a record appears twice or is skipped. Add a tiebreaker such as the id to the sort, so the ordering is total and paging is consistent.
Step 3: Watch the deep-offset cost
Offset paging is fine for the first pages, but a deep offset makes the database scan and discard many rows, which gets slow. For deep pagination use keyset paging, where the next page is fetched after the last item’s key, which stays fast.
Worked scenario
The endpoint binds paging and sorts by a stable key.
@GetMapping("/orders")
public Page<OrderDto> list(
@RequestParam(required = false) String status,
Pageable pageable) {
Pageable stable = PageRequest.of(
pageable.getPageNumber(), pageable.getPageSize(),
Sort.by("createdAt").descending().and(Sort.by("id")));
return repository.findByStatus(status, stable).map(OrderDto::from);
}Walk through the example
The controller builds a Pageable with a total sort order, adding id as a tiebreaker so equal createdAt values keep a deterministic order across pages. Without it, a page boundary could repeat or skip a row. The DTO keeps the API shape decoupled from the entity.
Common mistake
Paging without a stable sort, which causes duplicates and missing rows across pages, or allowing a client to request a huge page size, which defeats the purpose. Another is deep offset paging on a large table.
Verify the behavior
Request consecutive pages and assert that no record appears twice or is skipped. Test with many equal sort values to stress the tiebreaker. Measure a deep page and compare with keyset paging on the same data.
Interview exercise
Why add an id to the sort when paging?
Answer and reasoning
Because page boundaries are computed by position, and if two rows compare equal, their order between pages is undefined without a tiebreaker. A row could appear on two pages or none. Adding a unique key makes the ordering total, so each row belongs to exactly one page.
Continue learning
Compare query design in Keyset pagination and JPA behavior in JPA N+1. Read the Spring Data web pagination documentation and try the Spring Boot interview questions.