This topic combines three features in one API: search narrows the records, sorting orders them, and pagination returns them a page at a time.It is used for real listing screens, such as an admin table with a search box, sortable columns and page buttons.Spring Data handles all three in one repository method, with a Pageable that carries the page number, page size and sort order.The client sends keyword, page, size and sort as URL parameters, and the database applies WHERE, ORDER BY and LIMIT together.
Features
- One endpoint (
GET /students) handles search, sort and paging together. - Every parameter is optional, so any combination works.
- A department filter can be added on top of the keyword search.
- The response includes the records plus
totalElements,totalPages,hasNext,hasPreviousandsortedBy. - Sort fields are validated, and an invalid one returns
400instead of a server error. - Page size is capped at 50 through
application.properties.
Hands-on Experiment: Creating a spring boot application with Pagination, Sorting and Searching
The following steps are involved in developing this hands-on experiment.
Step 1: Generate the Project
Step 2: Project Structure
Step 3: pom.xml
Step 4: application.properties
Step 5: Create Entity Class
Step 6: Create Repository Interface
Step 7: Create Service Class
Step 8: Create Controller Class
Step 9: Main Application Class
Step 10: Run the Main Application Class
Step 1: Configure the Project on Spring Initializr
Go to start.spring.io and set:
- Project: Maven
- Language: Java
- Spring Boot version: 3.2.x
- Group: com.example
- Artifact: listing
- Name: listing
- Package name: com.example.listing
- Packaging: Jar
- Java: 17
- Dependencies: Spring Web, Spring Data JPA, MySQL Driver
Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.
Step 2: Generated Project Structure

Step 3: Explore pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>student-listing-demo</artifactId>
<version>1.0.0</version>
<name>student-listing-demo</name>
<properties>
<java.version>17</java.version>
</properties>
<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.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>Step 4: application.properties
spring.application.name=student-listing-demo
server.port=8082
spring.datasource.url=jdbc:mysql://localhost:3306/student_listing_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
# Page size limits
spring.data.web.pageable.default-page-size=5
spring.data.web.pageable.max-page-size=50Code language: PHP (php)
Step 5: Create Entity Class
package com.example.listing.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import java.time.LocalDate;
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String email;
private String department;
private Integer age;
@Column(name = "registration_date")
private LocalDate registrationDate;
public Student() {
}
public Student(String name, String email, String department, Integer age, LocalDate registrationDate) {
this.name = name;
this.email = email;
this.department = department;
this.age = age;
this.registrationDate = registrationDate;
}
@PrePersist
public void setDefaultRegistrationDate() {
if (registrationDate == null) {
registrationDate = LocalDate.now();
}
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public LocalDate getRegistrationDate() { return registrationDate; }
public void setRegistrationDate(LocalDate registrationDate) { this.registrationDate = registrationDate; }
}Step 6: Create below Repository Interface
package com.example.listing.repository;
import com.example.listing.entity.Student;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
// Search (keyword + optional department) + Sorting + Pagination in ONE query.
// The Pageable carries both the page details and the sort order.
// An empty keyword/department means "no filter".
@Query("SELECT s FROM Student s WHERE "
+ "(LOWER(s.name) LIKE LOWER(CONCAT('%', :keyword, '%')) OR "
+ " LOWER(s.email) LIKE LOWER(CONCAT('%', :keyword, '%')) OR "
+ " LOWER(s.department) LIKE LOWER(CONCAT('%', :keyword, '%'))) AND "
+ "(:department = '' OR LOWER(s.department) = LOWER(:department))")
Page<Student> searchStudents(@Param("keyword") String keyword,
@Param("department") String department,
Pageable pageable);
}
Step 7: Create below Service class
package com.example.listing.service;
import com.example.listing.entity.Student;
import com.example.listing.repository.StudentRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class StudentService {
// Only these fields may be used for sorting
private static final Set<String> ALLOWED_SORT_FIELDS =
Set.of("id", "name", "email", "department", "age", "registrationDate");
private final StudentRepository studentRepository;
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public Student save(Student student) {
return studentRepository.save(student);
}
public List<Student> saveAll(List<Student> students) {
return studentRepository.saveAll(students);
}
public Map<String, Object> listStudents(String keyword, String department, Pageable pageable) {
// 1. Validate sort fields
pageable.getSort().forEach(order -> {
if (!ALLOWED_SORT_FIELDS.contains(order.getProperty())) {
throw new IllegalArgumentException("Cannot sort by '" + order.getProperty()
+ "'. Allowed fields: " + ALLOWED_SORT_FIELDS);
}
});
// 2. Search + sort + paginate
Page<Student> page = studentRepository.searchStudents(clean(keyword), clean(department), pageable);
// 3. Build a simple response
Map<String, Object> response = new LinkedHashMap<>();
response.put("content", page.getContent());
response.put("currentPage", page.getNumber());
response.put("pageSize", page.getSize());
response.put("totalElements", page.getTotalElements());
response.put("totalPages", page.getTotalPages());
response.put("hasNext", page.hasNext());
response.put("hasPrevious", page.hasPrevious());
response.put("sortedBy", page.getSort().isSorted()
? page.getSort().stream()
.map(o -> o.getProperty() + " " + o.getDirection())
.collect(Collectors.toList())
: "unsorted");
return response;
}
private String clean(String value) {
return value == null ? "" : value.trim();
}
}
Step 8: Create Controller class
package com.example.listing.controller;
import com.example.listing.entity.Student;
import com.example.listing.service.StudentService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/students")
public class StudentController {
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
// POST /students
@PostMapping
public ResponseEntity<Student> create(@RequestBody Student student) {
return ResponseEntity.status(HttpStatus.CREATED).body(studentService.save(student));
}
// POST /students/bulk
@PostMapping("/bulk")
public ResponseEntity<List<Student>> createBulk(@RequestBody List<Student> students) {
return ResponseEntity.status(HttpStatus.CREATED).body(studentService.saveAll(students));
}
// GET /students?keyword=an&department=CSE&page=0&size=3&sort=age,desc
// Every parameter is optional.
@GetMapping
public Map<String, Object> list(@RequestParam(required = false) String keyword,
@RequestParam(required = false) String department,
@PageableDefault(size = 5, sort = "id", direction = Sort.Direction.ASC)
Pageable pageable) {
return studentService.listStudents(keyword, department, pageable);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleBadRequest(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
}
}
Step 9: Main Application
package com.example.listing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentListingApplication {
public static void main(String[] args) {
SpringApplication.run(StudentListingApplication.class, args);
}
}
Step 10: Run the StudentListingApplication


