Optimizing Large Dataset Retrieval with Spring Data JPA Pagination
Learn how to implement Spring Data JPA pagination and sorting to prevent memory overflows and optimize database performance using Pageable, Page, and Slice.
17 Oct 2025, 11:26 UTC

The Performance Cost of Unbounded Queries
Loading an entire database table into application memory causes OutOfMemoryError crashes and slows down response times as the dataset grows. To prevent this, you must implement pagination—the process of dividing a large result set into smaller, manageable chunks (pages) retrieved on demand.
Prerequisites
- A Spring Boot project with
spring-boot-starter-data-jpa. - An entity class mapped to a relational database table.
- A repository interface extending
PagingAndSortingRepositoryorJpaRepository.
Implementing Pageable Repositories
Spring Data JPA uses the Pageable interface to pass pagination and sorting instructions to the database. When you add a Pageable parameter to a repository method, Spring automatically appends the necessary LIMIT and OFFSET clauses to the generated SQL.
Repository Configuration
Extend your repository to support paging. While JpaRepository includes these features, PagingAndSortingRepository is the specific base for these operations.
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {
// Custom query method supporting pagination
Page<Product> findByCategory(String category, Pageable pageable);
}
Executing a Paged Request
To request data, use the PageRequest implementation. Note that Spring Data JPA uses zero-based indexing; the first page is 0, not 1.
// Run this in your Service layer
// Request page 0, 20 items per page, sorted by price descending
Pageable pageable = PageRequest.of(0, 20, Sort.by("price").descending());
Page<Product> productPage = productRepository.findByCategory("Electronics", pageable);
// Accessing the data
List<Product> content = productPage.getContent();
long totalElements = productPage.getTotalElements();
int totalPages = productPage.getTotalPages();
Choosing Between Page and Slice
The return type of your repository method significantly impacts database performance. Choosing the wrong one can lead to unnecessary overhead on large tables.
| Feature | Page<T> | Slice<T> |
|---|---|---|
| Count Query | Executes SELECT COUNT(*) |
No count query executed |
| Metadata | Total elements and total pages | Only knows if a next slice exists |
| Use Case | Numbered pagination UI (1, 2, 3...) | Infinite scroll or "Load More" buttons |
| Performance | Slower on massive tables | Faster (avoids full table scan) |
Verification and Diagnostics
To ensure pagination is working at the database level rather than in-memory, enable SQL logging in your application.properties:
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
Check for: Look for LIMIT ? OFFSET ? in the console output. If these are missing, the application is fetching all records and filtering them in Java, which will cause performance failure in production.
Testing the Result
Verify the Page object using a unit test to confirm the mapping logic:
- Offset Check: If the UI sends page 1, verify your service converts it to
PageRequest.of(0, size). - Sort Check: Pass
Sort.Direction.ASCand verify the first element ofgetContent()is the lowest value.
Limitations and Risks
Deep Pagination Degradation
Relational databases struggle with high offset values. For example, OFFSET 100000 LIMIT 20 requires the database to scan and discard 100,000 rows before returning 20. For extremely large datasets, consider Keyset Pagination (filtering by the last ID seen) instead of Pageable.
State Rollback
Because pagination is a read-only operation, there is no state to roll back in the database. However, if you change the Pageable implementation in a live API, ensure you update the frontend to handle the zero-based index to avoid skipping the first page of results.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.