package com.example.demo.controller;
import com.example.demo.dto.PageResponse;
import com.example.demo.dto.UserDTO;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class PaginatedUserController {
private final UserService userService;
public PaginatedUserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<PageResponse<UserDTO>> getUsers(
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC)
Pageable pageable
) {
Page<User> page = userService.findAll(pageable);
PageResponse<UserDTO> response = PageResponse.of(
page.map(this::toDto)
);
return ResponseEntity.ok(response);
}
@GetMapping("/search")
public ResponseEntity<PageResponse<UserDTO>> searchUsers(
@RequestParam String query,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "ASC") String sortDir
) {
Sort.Direction direction = Sort.Direction.fromString(sortDir);
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
Page<User> results = userService.search(query, pageable);
PageResponse<UserDTO> response = PageResponse.of(
results.map(this::toDto)
);
return ResponseEntity.ok(response);
}
@GetMapping("/active")
public ResponseEntity<PageResponse<UserDTO>> getActiveUsers(Pageable pageable) {
Page<User> page = userService.findByActive(true, pageable);
return ResponseEntity.ok(PageResponse.of(page.map(this::toDto)));
}
private UserDTO toDto(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setEmail(user.getEmail());
return dto;
}
}
package com.example.demo.dto;
import lombok.Data;
import org.springframework.data.domain.Page;
import java.util.List;
@Data
public class PageResponse<T> {
private List<T> content;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private boolean first;
private boolean last;
private boolean empty;
public static <T> PageResponse<T> of(Page<T> page) {
PageResponse<T> response = new PageResponse<>();
response.setContent(page.getContent());
response.setPageNumber(page.getNumber());
response.setPageSize(page.getSize());
response.setTotalElements(page.getTotalElements());
response.setTotalPages(page.getTotalPages());
response.setFirst(page.isFirst());
response.setLast(page.isLast());
response.setEmpty(page.isEmpty());
return response;
}
}
Spring Data provides elegant pagination and sorting mechanisms. Pageable interface defines page number, size, and sort parameters. Page wraps results with metadata—total elements, total pages, current page. Sort defines ordering by multiple properties. I use @PageableDefault for sensible defaults. Query methods accept Pageable automatically. Custom queries support pagination with Pageable parameters. DTOs should include pagination metadata for API clients. Offset-based pagination works for most cases; cursor-based pagination handles large datasets better. Sorting prevents inconsistent results across pages. Pagination reduces memory usage and improves response times. REST controllers use @RequestParam or Spring's automatic resolution. Proper pagination enhances user experience and system performance.