Pagination and Sorting with Spring Data JPA (Complete Guide with Example)

3 min read

Pagination and Sorting with Spring Data JPA (Complete Guide with Example)

In modern web applications, handling large datasets efficiently is essential. Instead of returning all records at once (which is inefficient and memory-intensive), pagination and sorting help return manageable chunks of data, optionally sorted by any field.

Spring Data JPA provides built-in support for both pagination and sorting, allowing developers to implement them with minimal effort.


Technologies Used

  • Java 17+
  • Spring Boot 3.x
  • Spring Data JPA
  • H2 (In-memory database for demo)
  • Maven

Project Setup

Add the following dependencies to your pom.xml:

<dependencies>
    <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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Entity Class

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private Double price;

    // Constructors, Getters and Setters
}

Repository Interface

Spring Data JPA provides the PagingAndSortingRepository and JpaRepository interfaces that support pagination and sorting.

public interface ProductRepository extends JpaRepository<Product, Long> {
}

Service Layer

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Page<Product> getAllProducts(int page, int size, String sortBy, String sortDir) {
        Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ?
                    Sort.by(sortBy).ascending() : Sort.by(sortBy).descending();

        Pageable pageable = PageRequest.of(page, size, sort);
        return productRepository.findAll(pageable);
    }
}

Controller Layer

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping
    public ResponseEntity<Map<String, Object>> getProducts(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "5") int size,
            @RequestParam(defaultValue = "id") String sortBy,
            @RequestParam(defaultValue = "asc") String sortDir) {

        Page<Product> pageProducts = productService.getAllProducts(page, size, sortBy, sortDir);

        Map<String, Object> response = new HashMap<>();
        response.put("products", pageProducts.getContent());
        response.put("currentPage", pageProducts.getNumber());
        response.put("totalItems", pageProducts.getTotalElements());
        response.put("totalPages", pageProducts.getTotalPages());

        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}

application.properties

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create

Data Initialization

@Component
public class DataInitializer implements CommandLineRunner {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public void run(String... args) {
        for (int i = 1; i <= 50; i++) {
            Product p = new Product();
            p.setName("Product " + i);
            p.setPrice(10.0 + i);
            productRepository.save(p);
        }
    }
}

Sample API Request

GET /api/products?page=1&size=10&sortBy=name&sortDir=desc

Response:

{
  "products": [
    { "id": 49, "name": "Product 49", "price": 59.0 },
    ...
  ],
  "currentPage": 1,
  "totalItems": 50,
  "totalPages": 5
}

Summary

FeatureDescription
PaginationAchieved using PageRequest.of(page, size)
SortingSort.by(field).ascending() or .descending()
CombinedUse PageRequest.of(page, size, sort)
Return ObjectPage<T> with metadata: content, total pages, total items

Best Practices

  • Validate page, size, and sortBy inputs.
  • Default values prevent API errors.
  • Avoid exposing entity classes directly (use DTOs in production).
  • For complex filtering, consider using Specification or QueryDSL.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

Your email address will not be published. Required fields are marked *