Spring Boot Pagination and Sorting with Spring Data JPA (Best Practices)
When building REST APIs, it’s critical to handle large database tables efficiently. Instead of returning thousands of records at once, we should return only a single page of data — with sorting and metadata like total pages, count, etc.
In this guide, you will learn everything about pagination and sorting using Spring Data JPA —
from basics to real-world advanced techniques 🚀.
1. Why Pagination Matters?
- Reduces memory consumption
- Faster API responses
- Better UX in tables and infinite scroll
- Scales easily with large datasets
2. Project Setup — Spring Boot + JPA
2.1 Maven dependencies
<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.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
We use H2 DB for simplicity, but you can replace with MySQL/Oracle.
3. Example Entity — Customer
package com.example.pagination.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "customers")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private Integer age;
}
4. Repository — Pageable and Sort
package com.example.pagination.repository;
import com.example.pagination.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
No extra code required — JpaRepository already provides pagination + sorting support!
5. Service — Pagination Logic
package com.example.pagination.service;
import com.example.pagination.entity.Customer;
import com.example.pagination.repository.CustomerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomerService {
private final CustomerRepository repository;
public Page<Customer> getCustomers(int page, int size, String sortBy, String dir) {
Sort sort = dir.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
return repository.findAll(pageable);
}
}
6. REST API — Dynamic Pagination + Sorting
| Method | Path | Description |
|---|---|---|
| GET | /api/customers | Paginated list |
package com.example.pagination.controller;
import com.example.pagination.entity.Customer;
import com.example.pagination.service.CustomerService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/customers")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService service;
@GetMapping
public ResponseEntity<Page<Customer>> getCustomers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String direction
) {
return ResponseEntity.ok(service.getCustomers(page, size, sortBy, direction));
}
}
7. Example API Calls
7.1 Page #0, size 5
GET http://localhost:8080/api/customers?page=0&size=5
7.2 Sort by age descending
GET http://localhost:8080/api/customers?sortBy=age&direction=desc
Response contains metadata:
{
"content": [...],
"totalPages": 3,
"totalElements": 12,
"size": 5,
"number": 0,
"numberOfElements": 5,
"last": false
}
8. Sorting on Multiple Fields
Sort sort = Sort.by("age").descending()
.and(Sort.by("name").ascending());
Pageable pageable = PageRequest.of(page, size, sort);
9. Common Pitfalls (and Fixes)
| Issue | Cause | Solution |
|---|---|---|
| Sorting on non-indexed columns is slow | DB scans entire table | Index frequently sorted fields |
| Bad user input breaks sorting | Invalid field in sortBy | Validate or use a whitelist |
| Large OFFSET values → slow | DB loads many rows before page | Use keyset pagination for huge pages |
10. Best Practices for Production
- Always provide
defaultpagination values - Expose only sorted columns allowed by API
- Prefer indexes on sorting fields
- Paginate at DB level, NEVER in Java code
11. Summary
You now know how to build fast and scalable paginated APIs using Spring Boot and JPA:
- Fetch only data needed for UI
- Dynamic sorting
- Metadata for pagination UI
- Multiple field sorting
- Security + performance considerations
Next steps: filtering, search pagination, keyset pagination with cursor based scrolling.