Spring Boot Pagination and Sorting with Spring Data JPA: Complete Guide

 

Spring Boot Pagination and Sorting with Spring Data JPA

Introduction

Imagine your application has 10 employees.

Returning all 10 records from a database isn't a problem.

Now imagine your application has 100,000 employees.

Returning every employee in a single REST API response would be inefficient and unnecessary.

This is where pagination becomes important.

Pagination divides a large result set into smaller pages so that an API can return only the records the client currently needs.

Sorting allows the client to control the order of those records.

For example:

GET /api/employees?page=0&size=10

can request the first 10 employees.

You could then sort them:

GET /api/employees?page=0&size=10&sort=name,asc

Spring Data provides Pageable, Page, PageRequest, and Sort abstractions specifically for these kinds of operations.

In this tutorial, you'll learn how to implement pagination and sorting in a Spring Boot REST API using Spring Data JPA.


What Is Pagination?

Pagination means dividing a large collection of records into smaller sections called pages.

For example, suppose your database contains 50 employees and you request:

page = 0
size = 10

The API returns the first 10 records.

Then:

page = 1
size = 10

returns the next 10 records.

The concept looks like this:

                 50 Employees
                      │
       ┌──────────────┼──────────────┐
       ↓              ↓              ↓
    Page 0          Page 1          Page 2
    1 - 10          11 - 20         21 - 30
                                      ...

This prevents the application from unnecessarily returning the entire dataset.


Why Is Pagination Important?

Without pagination:

Database
   ↓
100,000 records
   ↓
Application
   ↓
Huge JSON response
   ↓
Slow API

With pagination:

Database
   ↓
Requested page
   ↓
10 records
   ↓
Small JSON response
   ↓
Faster API

Pagination is particularly useful for:

  • Employee management systems
  • E-commerce applications
  • Product catalogs
  • Customer databases
  • Blog applications
  • Order management systems
  • Search results
  • Admin dashboards

What Is Sorting?

Sorting determines the order in which records are returned.

For example, employees could be sorted by name:

Adam
Brian
David
John
Michael

or by salary:

45000
55000
70000
85000
100000

Sorting can be:

Ascending

or:

Descending

For example:

name,asc

means alphabetical ascending order.

While:

salary,desc

means highest salary first.

Spring Data JPA supports dynamic sorting through Sort and Pageable.


Pagination and Sorting Architecture

Here's the flow we'll build:

Client
   │
   │ page=0&size=10&sort=name,asc
   ↓
Spring Boot Controller
   │
   ↓
Pageable
   │
   ↓
Service Layer
   │
   ↓
Repository
   │
   ↓
Spring Data JPA
   │
   ↓
Database
   │
   ↓
Page<Employee>
   │
   ↓
JSON Response

Create a Spring Boot Project

For this example, we'll use:

  • Spring Boot
  • Spring Web
  • Spring Data JPA
  • MySQL
  • Java

If you're following our earlier CRUD tutorial, you can use the same project.


Maven Dependencies

Make sure your pom.xml contains the required dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

Spring Boot's Data JPA starter provides the infrastructure needed to work with Spring Data JPA repositories.


Create the Employee Entity

Let's create a simple Employee entity.

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String department;

    private double salary;

    // Getters and setters
}

Our database might contain:

IDNameDepartmentSalary
1JohnIT70000
2SarahHR60000
3DavidIT80000
4EmmaFinance75000
5MichaelSales65000

Imagine that this table eventually contains thousands of records.

That's where pagination becomes useful.


Create the Repository

Create:

EmployeeRepository.java
public interface EmployeeRepository
        extends JpaRepository<Employee, Long> {

}

That's all we need.

We don't have to manually write SQL for basic pagination.

Spring Data provides pagination and sorting support through repository abstractions.


Using Pageable

The most important class in this tutorial is:

Pageable

It represents pagination information such as:

  • Page number
  • Page size
  • Sorting

For example:

Pageable pageable =
        PageRequest.of(0, 10);

This means:

Page: 0
Size: 10

What Is PageRequest?

PageRequest is an implementation of Pageable.

For example:

PageRequest.of(0, 10);

means:

Page number = 0
Page size   = 10

Spring Data's documentation uses PageRequest to construct pageable queries.


Important: Page Numbers Start at Zero

This is one of the most common things beginners get wrong.

Spring Data pagination is zero-based.

That means:

page=0 → first page
page=1 → second page
page=2 → third page

For example:

Page 0 → Records 1-10
Page 1 → Records 11-20
Page 2 → Records 21-30

Create a Paginated Repository Method

Because JpaRepository supports Pageable, we can write:

Page<Employee> findAll(Pageable pageable);

However, JpaRepository already provides a suitable findAll(Pageable) method, so you generally don't need to redeclare it.

That means our repository can remain:

public interface EmployeeRepository
        extends JpaRepository<Employee, Long> {

}

Create the Service Layer

Create:

EmployeeService.java
@Service
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public EmployeeService(
            EmployeeRepository employeeRepository) {

        this.employeeRepository =
                employeeRepository;
    }

    public Page<Employee> getEmployees(
            Pageable pageable) {

        return employeeRepository
                .findAll(pageable);
    }
}

The service receives the Pageable object and passes it to the repository.


Create the REST Controller

Now create:

EmployeeController.java
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(
            EmployeeService employeeService) {

        this.employeeService =
                employeeService;
    }

    @GetMapping
    public Page<Employee> getEmployees(
            Pageable pageable) {

        return employeeService
                .getEmployees(pageable);
    }
}

Spring can resolve pagination information from request parameters into a Pageable parameter.


Test Pagination

Start your application and request:

http://localhost:8080/api/employees?page=0&size=10

This requests:

Page = 0
Size = 10

The response contains page information along with the content.

A response can look conceptually like:

{
  "content": [
    {
      "id": 1,
      "name": "John",
      "department": "IT"
    }
  ],
  "number": 0,
  "size": 10,
  "totalElements": 50,
  "totalPages": 5
}

The exact JSON structure depends on your Spring Data/Spring Boot configuration.


Understanding Page<T>

When your repository returns:

Page<Employee>

you get more than just employee records.

A Page contains information such as:

Content
Current page
Page size
Total elements
Total pages
First/last indicators

This metadata is extremely useful for frontend applications.

For example:

Total records: 100
Page size: 10
Total pages: 10
Current page: 3

The Spring Data documentation explains that a Page provides total-element and total-page information, which requires a count query.


Add Sorting

Now let's add sorting.

Use:

sort=name,asc

For example:

http://localhost:8080/api/employees?page=0&size=10&sort=name,asc

This means:

Page: 0
Size: 10
Sort: name
Direction: ascending

Sort in Descending Order

To sort from highest to lowest or Z to A:

sort=name,desc

Example:

http://localhost:8080/api/employees?page=0&size=10&sort=name,desc

Spring Data supports sorting through the Pageable abstraction.


Sort by Salary

You can sort by another entity property:

sort=salary,desc

Example:

GET /api/employees?page=0&size=10&sort=salary,desc

The result will contain the highest-paid employees first.


Multiple Sorting Fields

You can also sort using multiple properties.

For example:

sort=department,asc&sort=name,asc

This means:

  1. Sort by department
  2. Then sort by employee name

Spring Data supports multiple sort expressions through Sort/Pageable.


Using Sort Directly

If you don't need pagination and only need sorting, you can use Sort.

Example:

Sort sort =
        Sort.by("name").ascending();

List<Employee> employees =
        employeeRepository.findAll(sort);

You can also create descending sorting:

Sort sort =
        Sort.by("salary").descending();

Spring Data JPA supports both Sort and Pageable as dynamic query parameters.


Pagination + Sorting Together

This is where PageRequest becomes particularly useful.

Pageable pageable =
        PageRequest.of(
                0,
                10,
                Sort.by("name").ascending()
        );

Now we have:

Page      → 0
Size      → 10
Sort      → name ASC

Then:

Page<Employee> employees =
        employeeRepository
                .findAll(pageable);

Using PageRequest in a Service

You can also create the Pageable object yourself.

public Page<Employee> getEmployees(
        int page,
        int size) {

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

    return employeeRepository
            .findAll(pageable);
}

Then add sorting:

public Page<Employee> getEmployees(
        int page,
        int size) {

    Pageable pageable =
            PageRequest.of(
                    page,
                    size,
                    Sort.by("name").ascending()
            );

    return employeeRepository
            .findAll(pageable);
}

A Better REST API Design

For a real application, you might expose:

GET /api/employees?page=0&size=10&sort=name,asc

This gives the frontend control over:

  • Which page to retrieve
  • How many records to retrieve
  • How records should be sorted

The complete request flow is:

GET /api/employees
        │
        ├── page=0
        ├── size=10
        └── sort=name,asc
                │
                ↓
             Pageable
                │
                ↓
          EmployeeRepository
                │
                ↓
             Database
                │
                ↓
             Page<Employee>

Pagination with a Custom Query

Pagination isn't limited to findAll().

Suppose we want employees from a particular department.

Page<Employee> findByDepartment(
        String department,
        Pageable pageable);

Then:

Page<Employee> employees =
        employeeRepository
            .findByDepartment(
                "IT",
                pageable
            );

Spring Data JPA supports Pageable parameters on query methods.


Create a Search + Pagination API

We can combine searching, pagination, and sorting.

Repository:

Page<Employee> findByNameContainingIgnoreCase(
        String name,
        Pageable pageable);

Controller:

@GetMapping("/search")
public Page<Employee> searchEmployees(
        @RequestParam String name,
        Pageable pageable) {

    return employeeRepository
            .findByNameContainingIgnoreCase(
                    name,
                    pageable);
}

Now the API can be called like:

/api/employees/search?name=john&page=0&size=10&sort=name,asc

This is much closer to what you'll find in a real-world application.


Page vs Slice

Spring Data provides both Page and Slice.

Page

A Page provides total information such as:

Total elements
Total pages
Current page

But determining that information can require an additional count query.

Slice

A Slice focuses on whether another portion of results is available.

For example:

Slice<Employee> findByDepartment(
        String department,
        Pageable pageable);

If your UI only needs:

Previous
Next

and doesn't need:

Page 1 of 250

a Slice may be more appropriate.

Spring Data specifically notes that a Slice avoids the total-count requirement that a Page uses.


Page vs Slice — Which Should You Use?

RequirementRecommended
Need total pagesPage
Need total recordsPage
Need only next/previousSlice
Large result traversalConsider Slice
Traditional numbered paginationPage

Don't automatically use Page for every query.

Choose based on what your application actually needs.


Protect the Page Size

One practical issue is allowing clients to request an extremely large page.

For example:

?page=0&size=1000000

You generally don't want an API consumer to request an enormous number of records.

Set a reasonable maximum page size at the application/API layer.

For example, your API might allow:

size <= 100

This is especially important for public APIs.


Validate Pagination Parameters

A robust API should also consider invalid values.

For example:

page=-1
size=0
size=100000

Don't assume every client sends valid pagination parameters.

You can define sensible defaults and enforce maximum values.


Pagination and Performance

Pagination improves API behavior, but it doesn't magically make every database query fast.

For example, a query with:

OFFSET 900000

can still become expensive on very large datasets.

For extremely large result sets, other approaches such as scrolling or keyset-style techniques may be more appropriate.

Current Spring Data JPA documentation discusses offset-based and keyset-based scrolling as alternatives for large datasets.


Offset Pagination vs Keyset Pagination

Traditional pagination commonly works around:

page
size
offset

Conceptually:

Database
   ↓
Skip records
   ↓
Return requested records

Keyset pagination works differently.

Instead of saying:

Give me page 500

you can say:

Give me records after this last ID

Conceptually:

Last record from previous request
             ↓
          Cursor
             ↓
       Next records

For extremely large datasets, keyset approaches can avoid some of the performance problems associated with deep offset pagination. Spring Data JPA's current documentation discusses keyset-based scrolling and its use of database indexes.


Common Pagination Mistakes

Mistake 1: Forgetting That Pages Start at Zero

Remember:

0 = first page
1 = second page
2 = third page

Mistake 2: Returning a List for Huge Data

Instead of:

List<Employee>

consider:

Page<Employee>

when the client needs pagination metadata.


Mistake 3: Allowing Unlimited Page Size

Don't allow:

size=1000000

without a good reason.


Mistake 4: Ignoring Database Performance

Pagination reduces the amount returned to the client, but database queries still need to be designed appropriately.


Mistake 5: Sorting by Uncontrolled Properties

If clients can provide arbitrary sort fields, validate the fields your API actually supports.

For example, explicitly allow:

name
salary
department

instead of accepting every possible property.


Best Practices for Spring Boot Pagination

Use Pageable

Let Spring Data handle pagination information rather than manually calculating offsets.

Use Page When Metadata Matters

If the UI needs total pages or total records, Page is useful.

Use Slice When Total Counts Aren't Needed

This can avoid an additional count query.

Set Maximum Page Size

Prevent clients from requesting huge result sets.

Validate Sort Fields

Don't blindly trust arbitrary sort properties from public clients.

Use Indexes

Frequently searched or sorted database columns may benefit from appropriate database indexes.

Consider Keyset Pagination for Very Large Data

Traditional page-number pagination isn't always ideal for extremely large datasets.


Frequently Asked Questions

What is pagination in Spring Boot?

Pagination allows a Spring Boot application to retrieve a limited portion of a larger dataset instead of returning all records at once.

What is Pageable in Spring Data JPA?

Pageable represents pagination and sorting information that can be passed to repository query methods.

What is PageRequest?

PageRequest is a Pageable implementation used to define page number, page size, and optionally sorting.

Does Spring Data JPA support sorting?

Yes. Spring Data JPA supports dynamic sorting through Sort and Pageable.

Are Spring Data page numbers zero-based?

Yes. The first page is page 0.

What is the difference between Page and Slice?

Page provides total-count and total-page information, while Slice focuses on whether another slice is available and does not require the total count.

Can pagination and sorting be used together?

Yes. PageRequest can contain both pagination and sorting information.

Example:

PageRequest.of(
    0,
    10,
    Sort.by("name").ascending()
);

Conclusion

Pagination and sorting are essential when building REST APIs that work with large amounts of data.

With Spring Data JPA, you don't need to manually write complicated pagination logic for common use cases.

The key classes and interfaces to remember are:

Pageable
PageRequest
Page
Slice
Sort

A typical request can look like:

GET /api/employees?page=0&size=10&sort=name,asc

The request flows through the application like this:

Client
  ↓
Controller
  ↓
Pageable
  ↓
Service
  ↓
Repository
  ↓
Database
  ↓
Page<Employee>
  ↓
JSON Response

Once you understand this pattern, you can apply it to employee systems, e-commerce products, customer records, orders, blog posts, and almost any application that needs to retrieve large collections efficiently.

Continue Learning Spring Boot

Muhammad Zeeshan Haider

Hello, I'm Muhammad Zeeshan, a Java Developer, Spring Boot Educator, and Technical Blogger. Through Java Web Action, I share practical tutorials, guides, and real-world examples on Java, Spring Boot, Microservices, REST APIs, JPA, Hibernate, MySQL, and software architecture. My goal is to help developers learn modern Java technologies and build professional applications with confidence.

Post a Comment

Please enter relevant questions and information.

Previous Post Next Post