Skillversitas

Pagination & Sorting

Introduction

When you have a large amount of data, returning it all at once is inefficient. Pagination splits the data into pages, and sorting orders the results as needed.

Basic Concepts

Concept Description
Page Page number (starting from 0)
Size Number of items per page
Sort Ordering (ASC/DESC)
Total Elements Total number of all items
Total Pages Number of available pages

Pageable & PageRequest

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

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public Page<Product> getAll(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id,asc") String[] sort) {

        List<Sort.Order> orders = new ArrayList<>();
        for (String s : sort) {
            String[] parts = s.split(",");
            orders.add(new Sort.Order(Sort.Direction.fromString(parts[1]), parts[0]));
        }

        Pageable pageable = PageRequest.of(page, size, Sort.by(orders));
        return productService.findAll(pageable);
    }
}

Request:

GET /api/products?page=0&size=10&sort=name,asc&sort=price,desc

Page Response

{
  "content": [
    {"id": 1, "name": "Laptop", "price": 15000000},
    {"id": 2, "name": "Mouse", "price": 150000}
  ],
  "pageable": {
    "pageNumber": 0,
    "pageSize": 10
  },
  "totalElements": 50,
  "totalPages": 5,
  "number": 0,
  "size": 10,
  "first": true,
  "last": false,
  "empty": false
}

Service Layer

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Page<Product> findAll(Pageable pageable) {
        return productRepository.findAll(pageable);
    }

    public Page<Product> searchByName(String keyword, Pageable pageable) {
        return productRepository.findByNameContaining(keyword, pageable);
    }
}

Repository with Pagination

public interface ProductRepository extends JpaRepository<Product, Long> {

    Page<Product> findByNameContaining(String keyword, Pageable pageable);

    Page<Product> findByPriceBetween(double min, double max, Pageable pageable);
}

Offset vs Cursor Pagination

Type How It Works Pros Cons
Offset page=3&size=10 (skips 30) Simple, can jump to a page Slow with very large data
Cursor cursor=abc123 (last ID) Consistent when data changes Cannot jump to a page

Cursor-based (manual):

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByIdGreaterThanOrderByIdAsc(Long id, Pageable pageable);
}

Sorting

@GetMapping("/sorted")
public List<Product> getAllSorted(
        @RequestParam(defaultValue = "name") String sortBy,
        @RequestParam(defaultValue = "asc") String direction) {

    Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
    return productRepository.findAll(sort);
}

Multiple fields:

Sort sort = Sort.by(
    Sort.Order.desc("price"),
    Sort.Order.asc("name")
);
return productRepository.findAll(sort);

Summary

  • PageRequest.of(page, size, sort) creates a Pageable object.
  • Page<T> automatically includes metadata (totalElements, totalPages).
  • Offset is good for UIs, cursor is good for infinite scroll.

Next chapter: We'll cover Custom Query — how to write custom queries using JPQL and native SQL.