Hook: Sorting is one of those tiny API features that makes a service feel polished instead of random.
Question: What does sorting mean in Spring Data, and how do you use it in a Spring Boot REST API?
Answer: In Spring Data, sorting means asking a repository to return rows in a specific order, such as newest first or cheapest first. In REST APIs, Spring MVC can turn query parameters like ?sort=price,desc into a Sort object automatically, and you pass that into repository methods like findAll(sort) or findAll(pageable). The key detail is that Spring Data sorts by entity property names, not database column names.
Interview-Ready Answer: I use Spring Data sorting by passing a Sort or Pageable into repository methods. In a REST API, Spring Boot can bind query params like ?sort=price,desc&sort=name,asc directly, so the client controls the order without me writing SQL. If there is no sort, I usually set a safe default, and I always remember that the sort field must match the JPA entity property name, not the table column.
Detailed Explanation: A Sort object is a small, immutable value object that describes order rules, such as ascending by price and then ascending by name. Spring Data does not sort by magic inside your controller; it passes that intent down to the repository layer so the database can add an ORDER BY clause.
/products?sort=price,desc&sort=name,asc.Sort or Pageable argument. Sort only describes ordering, while Pageable describes ordering plus page number and page size.findAll(sort) or findAll(pageable).ORDER BY on the entity property path.price,desc, name,asc, or multiple fields without changing code.Sort when the client only needs ordering.Pageable when the client needs both ordering and paging, which is common for search screens and admin tables.@Query ORDER BY when the order is business-critical and should not be client-controlled.| Approach | Best for | Pros | Watch out |
|---|---|---|---|
Sort | Simple ordering | Flexible, clean API | No paging |
Pageable | Tables, lists | Sort + page + size | Default size is often 20 |
@Query ORDER BY | Fixed order | Very explicit | Less flexible |
O(n log n); in Spring Data JPA, the database does the work, so performance depends on indexes and row count.sort=created_at usually fails if the entity field is createdAt. Spring Data uses Java property names, not table column names.price,desc then name,asc means price decides first, and name breaks ties.0 and size 20 unless you override it.Memory note: think of Sort as the order on a shelf and Pageable as which slice of that shelf you take.
Real-World Example: In an e-commerce catalog service, the frontend product grid often sends requests like /products?sort=price,asc or /products?page=0&size=24&sort=createdAt,desc. The backend uses Spring Data sorting so shoppers can switch between cheapest, newest, and best-selling views without new endpoints. If someone mistakenly sends a database column name such as sort=created_at instead of the entity field createdAt, the service can throw a data-access error and the product page returns a 400 or 500. In production, that shows up as blank grids, stack traces mentioning PropertyReferenceException, and angry support tickets saying, "the filter works but the sort is broken."
# File: src/main/resources/application.properties
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# File: src/main/java/com/example/sorting/SortingApplication.java
package com.example.sorting;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.mapping.PropertyReferenceException;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@SpringBootApplication
public class SortingApplication {
public static void main(String[] args) {
SpringApplication.run(SortingApplication.class, args);
}
@Bean
CommandLineRunner seed(ProductRepository repository) {
return args -> repository.saveAll(List.of(
new Product("Keyboard", "Electronics", new BigDecimal("79.99")),
new Product("Mouse", "Electronics", new BigDecimal("29.99")),
new Product("Desk Chair", "Furniture", new BigDecimal("199.00")),
new Product("Notebook", "Stationery", new BigDecimal("4.50"))
));
}
}
@Entity
@Table(name = "products")
class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String category;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
protected Product() {
// JPA needs a no-arg constructor.
}
Product(String name, String category, BigDecimal price) {
this.name = name;
this.category = category;
this.price = price;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getCategory() {
return category;
}
public BigDecimal getPrice() {
return price;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCategory(String category) {
this.category = category;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
interface ProductRepository extends JpaRepository<Product, Long> {
// Spring Data automatically gives us findAll(Sort) and findAll(Pageable).
}
@RestController
@RequestMapping("/products")
class ProductController {
private final ProductRepository repository;
ProductController(ProductRepository repository) {
this.repository = repository;
}
@GetMapping
List<Product> all(Sort sort) {
// Spring MVC binds ?sort=price,desc&sort=name,asc into this Sort object.
// If the client does not send a sort, keep the output stable with a safe default.
Sort effectiveSort = (sort == null || sort.isUnsorted())
? Sort.by("id").ascending()
: sort;
return repository.findAll(effectiveSort);
}
@GetMapping("/page")
Page<Product> page(Pageable pageable) {
// Pageable carries page, size, and sort together.
// Default page size is 20 unless the client overrides it.
return repository.findAll(pageable);
}
}
@RestControllerAdvice
class ApiErrors {
@ExceptionHandler({PropertyReferenceException.class, InvalidDataAccessApiUsageException.class})
ResponseEntity<Map<String, String>> badSort(Exception ex) {
// A typo like sort=created_at,desc becomes a clean 400 instead of a stack trace.
return ResponseEntity.badRequest().body(Map.of(
"error", "Invalid sort field. Use the entity property name, not the column name.",
"details", ex.getMessage()
));
}
}Follow-up & Tricky Questions:
Sort different from Pageable? Sort only controls order, while Pageable controls page number, page size, and order together. In Spring Data REST APIs, Pageable is usually the better choice when the client needs a grid or list view.customer.address.city. Be careful: nested sorts often imply joins, which can be slower on large tables.PropertyReferenceException. In an API, that should become a clear 400 response, not a cryptic stack trace.?sort=price, what direction is used? Ascending by default. The direction only changes to descending when you say ,desc.Pageable, does it sort before or after paging? Sort happens first, then paging. That is why page 0 and page 1 are consistent slices of the same order.Common Mistakes:
created_at because that is the database column. Correction: sort by the entity field, like createdAt.Pageable for API lists that can grow.Memory Hook: Think of Sort like the order of books on a shelf, and Pageable like which shelf slice you take home. The shelf order is the sort; the slice is the page.
Cheat Sheet:
Sort = order only.Pageable = page + size + sort.Practice Tasks:
/products?sort=price,desc and verify the order changes.