Filtering and Searching In API
Filtering and searching are essential features in REST APIs, especially when dealing with large datasets. They allow clients to retrieve only the relevant data, improving performance and usability.
We can do effective filtering, searching and sorting in the following ways -
1. Using Query Parameters
Query parameters in a REST API are key-value pairs appended to the URL after a ?
. For filtering and searching:
Standardized Syntax: Use clear and intuitive names for query parameters:
field=value
: Simple equality filter (e.g.,/users?role=admin
).field[operator]=value
: For advanced filtering using operators ([gt]
,[lt]
,[gte]
,[lte]
, etc.):Combine filters with logical operators like
AND
orOR
.
Consistent Format: Ensure naming conventions are consistent:
Use camelCase (
userName
,minPrice
) or snake_case (user_name
,min_price
) based on your API style guide.
Reserved Characters: Encode special characters properly to avoid parsing issues.
The simplest way to implement filtering is by using query parameters in the URL. Spring Boot makes it easy to handle query parameters with @RequestParam
2: Using Specifications (Dynamic Queries)
For more complex filtering, you can use Specifications with JPA Criteria API. This approach is flexible and allows you to build dynamic queries.
public class SearchCriteria {
private String keyword;
private String fieldName;
private String operator; // e.g., EQUAL, GREATER_THAN
private Object value;
// Getters and Setters
}
public class GenericSpecification<T> implements Specification<T> {
private SearchCriteria criteria;
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (criteria.getOperator().equalsIgnoreCase("EQUAL")) {
return builder.equal(root.get(criteria.getFieldName()), criteria.getValue());
}
if (criteria.getOperator().equalsIgnoreCase("GREATER_THAN")) {
return builder.greaterThan(root.get(criteria.getFieldName()), criteria.getValue().toString());
}
// Add other conditions here
return null;
}
}
3. Combining Filtering and Searching
We can combine filtering and searching in a single endpoint to provide a powerful querying mechanism.
Ex. /products?category=electronics&price[gt]=100&brand=Samsung
Using spring data specification we can combine multiple specifications -
Specification spec = Specification.where(spec1).and(spec2).or(spec3);
4. Pagination and Sorting
We can utilize effective pagination and sorting based on our need to display properly ordered data in chunks.
Query Parameter for Sorting: Use a
sort
query parameter:/products?sort=price,desc
field
: Field to sort by (e.g.,price
).direction
: Sorting direction (asc
ordesc
).
Multiple Sort Fields: Support sorting by multiple fields:
/products?sort=price,asc&sort=name,desc
Spring Data PagingAndSortingRepository: Use the
Pageable
interface:@GetMapping("/products") public Page<Product> getProducts(Pageable pageable) { return productRepository.findAll(pageable); }
Example
Pageable
request:bashCopyEdit/products?page=0&size=10&sort=price,desc
Example -
Simple -
Filter record based on exact match
GET /api/users?role=admin
Rangle Filters
Support range queries using operators like
gt
,lt
,gte
,lte
.GET /api/products?price[gt]=100&price[lte]=500
Multiple Filters
GET /api/orders?status=delivered&date[gte]=2023-01-01&date[lte]=2023-12-31
Full Text Search
GET /api/books?search=Spring Boot
Logical Operators
GET /api/movies?filter=(genre=action AND rating[gte]=8) OR (director=Nolan)
Sorting
GET /api/products?sort=price,asc
GET /api/products?sort=price,desc&sort=name,asc
Pagination
GET /api/users?page=1&size=10
Filtering on Nested or Related Fields
GET /api/orders?customer.name=John&product.category=electronics
Retrieves orders where the
customer
’s name isJohn
and theproduct
belongs to theelectronics
category.Case-Insensitive Search
GET /api/users?email=example@example.com
Matches
example@example.com
,Example@example.com
, etc.Include/Exclude Fields
GET /api/users?fields=id,name,email
Includes only the
id
,name
, andemail
fields in the response.Reserved Characters Handling
Ensure special characters in query parameters are encoded.
/products?name=Spring%20Boot&description=advanced%2Fbeginner
Retrieves products where the
name
isSpring Boot
and thedescription
containsadvanced/beginner
.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class ProductController {
@Autowired
private ProductService productService;
// 1. Simple Equality Filters
@GetMapping("/users")
public List<User> getUsersByRole(@RequestParam(required = false) String role) {
return productService.getUsersByRole(role);
}
// 2. Range Filters
@GetMapping("/products")
public List<Product> getProductsByPriceRange(
@RequestParam(required = false) Double priceGt,
@RequestParam(required = false) Double priceLte
) {
return productService.getProductsByPriceRange(priceGt, priceLte);
}
// 3. Full-Text Search
@GetMapping("/books")
public List<Book> searchBooks(@RequestParam(required = false) String search) {
return productService.searchBooks(search);
}
// 4. Logical Operators
@GetMapping("/movies")
public List<Movie> getMoviesWithFilters(
@RequestParam(required = false) String filter
) {
return productService.getMoviesWithFilters(filter);
}
// 5. Sorting
@GetMapping("/products/sorted")
public List<Product> getSortedProducts(
@RequestParam(required = false, defaultValue = "id,asc") String[] sort
) {
List<Sort.Order> orders = new ArrayList<>();
if (sort[0].contains(",")) {
// sort=[field,direction] format
for (String sortOrder : sort) {
String[] _sort = sortOrder.split(",");
orders.add(new Sort.Order(
getSortDirection(_sort[1]),
_sort[0]));
}
} else {
// sort=[field] format
orders.add(new Sort.Order(
getSortDirection("asc"),
sort[0]));
}
Pageable pageable = PageRequest.of(page, size, Sort.by(orders));
return ResponseEntity.ok(
productService.findProducts(name, minPrice, maxPrice,
categories, pageable));
}
// 6. Pagination
@GetMapping("/users/paginated")
public Page<User> getUsersWithPagination(Pageable pageable) {
return productService.getUsersWithPagination(pageable);
}
// 7. Nested/Related Field Filtering
@GetMapping("/orders")
public List<Order> getOrdersByCustomerAndCategory(
@RequestParam(required = false) String customerName,
@RequestParam(required = false) String productCategory
) {
return productService.getOrdersByCustomerAndCategory(customerName, productCategory);
}
// 8. Case-Insensitive Search
@GetMapping("/users/search")
public List<User> getUsersByEmail(@RequestParam String email) {
return productService.getUsersByEmail(email);
}
// 9. Include/Exclude Fields
@GetMapping("/users/fields")
public List<User> getUsersWithSelectedFields(@RequestParam List<String> fields) {
return productService.getUsersWithSelectedFields(fields);
}
}
Summary
Filtering: Use query parameters or Specifications to narrow down results.
Searching: Implement simple or advanced search using query parameters or Specifications.
Combining Filtering and Searching: Provide a unified endpoint for flexible querying.
Pagination and Sorting: Improve performance and usability by paginating and sorting results.
Last updated