πŸ”₯ Launch Offer: Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999 β€’ 75% OFF. Offer expires in πŸ”₯ Launch Offer: Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999 β€’ 75% OFF. Offer expires in Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999

00:00:00 Get Bundle
home spring boot spring-boot-pagination-with-spring-data-jpa-a-complete-end-to-end-guide
Interview guide Spring Boot 24 sections

Spring Boot Pagination with Spring Data JPA: A Complete End-to-End Guide

When building REST APIs, returning all records from a database table is rarely a good idea. Imagine a Product table with 10 million records. Returning every record in…

Start reading
By javainterviewprep July 29, 2026 6 min read

When building REST APIs, returning all records from a database table is rarely a good idea. Imagine a Product table with 10 million records. Returning every record in a single API response would consume excessive memory, increase response time, and degrade the user experience.

This is where pagination comes in.

Pagination allows clients to request only a subset of records at a time. Instead of returning all products, the API can return 10 or 20 records per request, along with metadata such as the current page number, total pages, and total records.

Spring Data JPA makes pagination extremely easy through the Page, Pageable, and PageRequest classes.

In this article, we’ll implement pagination step by step and understand what happens internally.

Why Do We Need Pagination?

Suppose your database contains 100,000 products.

Without pagination:

GET /v1/products

The API would return all 100,000 products.

Problems:

  • High memory consumption
  • Slow API response
  • Increased network traffic
  • Poor frontend performance
  • Bad user experience

Instead, clients can request only the required data.

Example:

GET /v1/products?page=0&size=10

This returns only the first 10 products.

Pagination Parameters

Most REST APIs accept the following query parameters.

ParameterDescriptionExample
pageCurrent page number (0-based)0
sizeNumber of records per page10
sortByColumn used for sortingprice
directionSorting orderasc / desc

Example request:

GET /v1/products?page=0&size=10&sortBy=price&direction=desc

Meaning:

  • Return the first page
  • Maximum 10 products
  • Sort by price
  • Highest price first

Project Structure

controller
    ProductController

service
    ProductService
    ProductServiceImpl

repository
    ProductRepository

entity
    Product

dto
    ProductResponse
    PaginationResponse
    ApiResponse

Step 1: Repository

One of the biggest advantages of Spring Data JPA is that pagination support is already built into JpaRepository.

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

}

No additional method is required.

Internally, Spring provides methods like:

Page<T> findAll(Pageable pageable);

Step 2: Service Interface

public interface ProductService {

    ApiResponse<PaginationResponse<ProductResponse>> getAllProducts(
            int page,
            int size,
            String sortBy,
            String direction);

}

The service receives all pagination parameters from the controller.

Step 3: Controller

@GetMapping
public ResponseEntity<ApiResponse<PaginationResponse<ProductResponse>>> getProducts(

        @RequestParam(defaultValue = "0") int page,

        @RequestParam(defaultValue = "10") int size,

        @RequestParam(defaultValue = "id") String sortBy,

        @RequestParam(defaultValue = "asc") String direction) {

    return ResponseEntity.ok(
            productService.getAllProducts(
                    page,
                    size,
                    sortBy,
                    direction));
}

Understanding @RequestParam

If the client doesn’t pass any values,

GET /v1/products

Spring automatically assigns

page = 0
size = 10
sortBy = id
direction = asc

Step 4: Creating the Sort Object

The first step inside the service is creating the sorting instruction.

Sort sort = direction.equalsIgnoreCase("desc")
        ? Sort.by(sortBy).descending()
        : Sort.by(sortBy).ascending();

Suppose the client calls

GET /v1/products?sortBy=price&direction=desc

Spring receives

sortBy = "price";
direction = "desc";

Since direction is “desc”, Spring creates

Sort.by("price").descending();

This object represents

ORDER BY price DESC

Notice that no SQL is executed here.

The Sort object simply stores the sorting instruction.

Understanding sortBy vs direction

Many beginners confuse these two parameters.

sortBy

Specifies which column should be used for sorting.

Examples:

name

price

createdAt

quantity

direction

Specifies how the records should be sorted.

asc

desc

Example:

GET /products?sortBy=name&direction=asc

Result

Apple

Laptop

Mouse

Phone

Another example

GET /products?sortBy=price&direction=desc

Result

75000

45000

1200

500

Step 5: Creating Pageable

Now we combine pagination and sorting.

Pageable pageable = PageRequest.of(page, size, sort);

Suppose

page = 1

size = 5

sort = price DESC

Spring creates a Pageable object containing

Page Number : 1

Page Size : 5

Sort By : price

Direction : DESC

Still, no SQL query has been executed.

The Pageable object is simply carrying pagination information.

What Does Page Size Mean?

Suppose your table has 12 products.

1 Laptop
2 Mouse
3 Keyboard
4 Phone
5 Monitor
6 Tablet
7 Printer
8 Camera
9 SSD
10 Speaker
11 Hard Disk
12 Webcam

If

GET /products?page=0&size=5

Output

Laptop
Mouse
Keyboard
Phone
Monitor

Only five records are returned.

Next page

GET /products?page=1&size=5

Output

Tablet
Printer
Camera
SSD
Speaker

Third page

GET /products?page=2&size=5

Output

Hard Disk
Webcam

Only two records remain.

So

Page Size = Maximum number of records returned per page.

It does not mean the database contains only five records.

Step 6: Fetching Data

Page<Product> productPage = repository.findAll(pageable);

This is the point where SQL is generated.

Suppose

page = 1

size = 5

sort = price DESC

Spring generates SQL similar to

SELECT *
FROM product
ORDER BY price DESC
LIMIT 5 OFFSET 5;

Understanding LIMIT

LIMIT 5

means

Return only five records.

Understanding OFFSET

OFFSET 5

means

Skip the first five records.

Formula

Offset = Page Number Γ— Page Size

Examples

PageSizeOffset
0100
11010
21020
31030

What is Page?

Most beginners think findAll(pageable) returns only a List.

It doesn’t.

It returns a Page.

Page<Product> productPage

The Page object contains

  • List of products
  • Current page
  • Page size
  • Total records
  • Total pages
  • Whether it is the first page
  • Whether it is the last page
  • Whether next page exists
  • Whether previous page exists

Example

Page

content
--------
Laptop
Phone
Mouse

page = 0

size = 3

totalElements = 12

totalPages = 4

first = true

last = false

Step 7: Convert Entity to DTO

Never expose entities directly.

Instead,

Page<ProductResponse> response =
        productPage.map(this::mapToResponse);

Spring internally converts every Product into ProductResponse.

Equivalent code

List<ProductResponse> responses = new ArrayList<>();

for(Product product : productPage.getContent()){

    responses.add(mapToResponse(product));
}

The beauty of map() is that it preserves all pagination metadata.

Only the object type changes.

Page<Product>

↓

Page<ProductResponse>

Everything else remains unchanged.

Custom Pagination Response

Returning Spring’s Page object exposes unnecessary internal details.

Instead create

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PaginationResponse<T>{

    private List<T> content;

    private int page;

    private int size;

    private long totalElements;

    private int totalPages;

    private boolean first;

    private boolean last;

    private boolean hasNext;

    private boolean hasPrevious;
}

This gives complete control over the API response.

Sample API Request

GET /v1/products?page=0&size=5&sortBy=price&direction=desc

Sample Response

{
  "success": true,
  "message": "Products fetched successfully",
  "data": {
    "content": [
      {
        "id": 5,
        "name": "Laptop",
        "price": 75000
      },
      {
        "id": 2,
        "name": "Phone",
        "price": 60000
      }
    ],
    "page": 0,
    "size": 5,
    "totalElements": 120,
    "totalPages": 24,
    "first": true,
    "last": false,
    "hasNext": true,
    "hasPrevious": false
  }
}

Complete Flow

Client

↓

GET /products?page=0&size=5&sortBy=price&direction=desc

↓

Controller

↓

Service

↓

Create Sort

↓

Create Pageable

↓

Repository.findAll(pageable)

↓

Spring Data JPA

↓

SQL

↓

Database

↓

Page<Product>

↓

Page<ProductResponse>

↓

Controller

↓

JSON Response

Best Practices

  • Always use pagination for list APIs.
  • Return DTOs instead of entities.
  • Validate page and size to avoid invalid requests.
  • Restrict maximum page size (for example, 100) to prevent abuse.
  • Validate sortBy against allowed fields to avoid invalid property errors.
  • Return a custom pagination DTO instead of exposing Spring’s Page directly.
  • Combine pagination with filtering and sorting for production-grade APIs.

Conclusion

Pagination is an essential feature for scalable REST APIs. Spring Data JPA simplifies its implementation using Page, Pageable, and PageRequest, allowing you to fetch only the required records while automatically providing useful metadata such as total pages, total elements, and navigation information.

A typical flow is:

  1. Receive pagination and sorting parameters in the controller.
  2. Create a Sort object based on sortBy and direction.
  3. Build a Pageable using PageRequest.
  4. Pass the Pageable to the repository.
  5. Convert the resulting Page<Entity> into a Page<DTO>.
  6. Return a clean, custom pagination response to the client.

By following these practices, your APIs remain efficient, scalable, and easy for frontend applications to consume.