By WebNest Studio Engineering Team
Spring Boot Tutorial
Pagination and Sorting with Pageable
Returning an entire table in one response does not scale — a products table with 500,000 rows should never be fetched in a single query for a listing page. Spring Data JPA builds pagination and sorting directly into the repository abstraction through the Pageable and Page types, so paging works consistently across every repository without custom SQL.
Pageable and PageRequest
Pageable is an interface describing a page request: which page number, how many items per page, and optional sort order. PageRequest.of(page, size, sort) is the standard way to construct one. Any repository method can accept a Pageable parameter as long as it either overrides a JpaRepository method or is declared to return a Page/Slice/List.
The Page<T> Response
A repository method returning Page<T> gives back not just the requested slice of data but also metadata: total number of elements, total number of pages, whether this is the first/last page, and the current page number. This is exactly the metadata a frontend needs to render pagination controls without a separate count query.
Accepting Pagination Parameters from the Client
Spring MVC can bind an incoming Pageable directly from query parameters (e.g. ?page=0&size=20&sort=price,desc) when a controller method parameter is typed as Pageable — Spring Data Web support resolves it automatically, so you rarely construct PageRequest by hand inside a controller.
Defaults and Limits
When the client sends no paging parameters, Spring Boot uses page 0 with 20 items and no sort. @PageableDefault(size = 10, sort = "price", direction = DESC) on the controller parameter sets a different default for one endpoint. Clients can never ask for an unlimited page: size is capped at 2000 by default, and you can lower that cap with spring.data.web.pageable.max-page-size. A request for ?size=5000 then quietly gets the cap instead.
Returning Stable JSON
If a controller returns a Page directly, Spring Data logs a warning on the first request: "Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure". The default JSON carries internal fields such as pageable.paged and sort.unsorted, and their names may change between versions. Set spring.data.web.pageable.serialization-mode=via-dto and the response becomes a small, stable shape: content plus a page object with size, number, totalElements and totalPages. If you configure this with @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) instead, note that declaring that annotation turns off Spring Boot's own paging configuration, so the spring.data.web.pageable.* properties above stop working.
Page or Slice?
Page runs a second COUNT query so it can report totalElements and totalPages, which you need for numbered pagination ("page 3 of 12"). Slice skips the count and only knows whether another slice exists, which is all an infinite-scroll feed or a "Load more" button needs. On a large table the count query can cost as much as the page itself, so choose Slice when nobody looks at the total. For very deep pages, offset paging gets slower because the database still walks past every skipped row; Spring Data's keyset scrolling (ScrollPosition and Window) avoids that by continuing from the last row seen.
Example
Paginating and sorting a product listing endpoint (tested on Spring Boot 4.1.1)
public interface ProductRepository extends JpaRepository<Product, Long> {
Page<Product> findByCategory(String category, Pageable pageable);
Slice<Product> findSliceByCategory(String category, Pageable pageable);
}
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductRepository repository;
public ProductController(ProductRepository repository) { this.repository = repository; }
@GetMapping
public Page<Product> list(@RequestParam String category,
@PageableDefault(size = 2, sort = "price", direction = Sort.Direction.DESC) Pageable pageable) {
return repository.findByCategory(category, pageable);
}
@GetMapping("/feed")
public Slice<Product> feed(@RequestParam String category, Pageable pageable) {
return repository.findSliceByCategory(category, pageable);
}
}
# application.properties
spring.data.web.pageable.serialization-mode=via-dto
spring.data.web.pageable.max-page-size=50
GET /api/products?category=Electronics
{"content":[{"name":"Mechanical Keyboard","category":"Electronics","price":89.00,"stock":12,"id":3},
{"name":"Phone Stand","category":"Electronics","price":14.50,"stock":14,"id":2}],
"page":{"size":2,"number":0,"totalElements":3,"totalPages":2}}
GET /api/products?category=Electronics&size=5000
-> "page":{"size":50,"number":0,"totalElements":3,"totalPages":1} (size capped at 50)
GET /api/products/feed?category=Electronics&size=2
-> a Slice: "content", "first", "last", "number", "size" ... but no totalElements or totalPages
Common Mistakes
- Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable.
- Confusing Page (includes total count, an extra query) with Slice (no total count, cheaper) when only "has more" is actually needed.
- Leaving the page size cap at its default of 2000 for an endpoint that returns heavy objects. Lower it with spring.data.web.pageable.max-page-size.
- Assuming page numbers are 1-based — Spring Data's Pageable page numbers are 0-based.
- Returning Page from a controller without serialization-mode=via-dto, so the frontend depends on a JSON shape that Spring Data explicitly says is not stable.
- Adding @EnableSpringDataWebSupport to a Spring Boot app and then wondering why spring.data.web.pageable.* properties no longer have any effect.
- Sorting by a field the client controls without checking it. An unknown sort property makes the query fail, so validate or whitelist sortable fields on public APIs.
Exercise
A teammate is about to make this mistake: Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable. Use Pageable and PageRequest to review the decision. Write a corrected implementation or design sketch that follows Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one., then name one edge case you would check.
Show hints
- Start with Pageable and PageRequest and compare your approach with Paginating and sorting a product listing endpoint (tested on Spring Boot 4.1.1). Identify the decision or behavior the lesson recommends.
- Walk through your scenario step by step. Confirm that it follows Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one. and that it would catch the failure described by Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable..
Quick quiz
1. Which choice best describes the recommended approach in Pagination and Sorting with Pageable?
- A. Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable.
- B. Confusing Page (includes total count, an extra query) with Slice (no total count, cheaper) when only "has more" is actually needed.
- C. Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one.
Show answer
C. This is the recommended approach: Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one. Avoid: Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable.
Interview questions
Why does this principle matter in Pagination and Sorting with Pageable: Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one.
Show answer
Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one. Apply this in Pageable and PageRequest; the example Paginating and sorting a product listing endpoint (tested on Spring Boot 4.1.1) shows one way to do it. Avoid Fetching the entire table into memory and paginating it in Java code instead of pushing LIMIT/OFFSET down to the database via Pageable. and explain how your implementation or design prevents that failure.
Key Points to Remember
- Pageable describes a page request (number, size, sort); PageRequest.of() is the standard way to build one.
- Page<T> returns the requested data slice plus pagination metadata (total elements, total pages, first/last).
- A Pageable controller parameter is automatically bound from query parameters like page, size, and sort.
- Page numbers in Spring Data are zero-based, not one-based.
- Defaults are page 0, size 20, max size 2000; @PageableDefault and spring.data.web.pageable.* change them.
- Use serialization-mode=via-dto for a stable JSON shape, and Slice when the total count is not needed.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering