The Specification pattern helps you build flexible and reusable database queries step-by-step without writing complex SQL. It makes filtering data easier and cleaner.
Specification pattern for dynamic queries in Spring Boot
public class YourEntitySpecification implements Specification<YourEntity> { private SearchCriteria criteria; public YourEntitySpecification(SearchCriteria criteria) { this.criteria = criteria; } @Override public Predicate toPredicate(Root<YourEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder) { switch (criteria.getOperation()) { case ">": return builder.greaterThan(root.get(criteria.getKey()), criteria.getValue().toString()); case "<": return builder.lessThan(root.get(criteria.getKey()), criteria.getValue().toString()); case ":": if (root.get(criteria.getKey()).getJavaType() == String.class) { return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%"); } else { return builder.equal(root.get(criteria.getKey()), criteria.getValue()); } default: return null; } } }
The Specification interface requires implementing the toPredicate method.
Use CriteriaBuilder to build conditions like equals, greater than, or like.
public class ProductSpecification implements Specification<Product> { private SearchCriteria criteria; public ProductSpecification(SearchCriteria criteria) { this.criteria = criteria; } @Override public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder builder) { if (criteria.getOperation().equalsIgnoreCase(":")) { return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%"); } return null; } }
Specification<Product> spec = Specification.where(new ProductSpecification(new SearchCriteria("price", ">", 100))) .and(new ProductSpecification(new SearchCriteria("category", ":", "Books")));
This code defines a reusable specification for filtering products by name containing 'phone' and price greater than 500.
You can pass this specification to Spring Data JPA repository methods to get filtered results.
import org.springframework.data.jpa.domain.Specification; import jakarta.persistence.criteria.*; public class SearchCriteria { private String key; private String operation; private Object value; public SearchCriteria(String key, String operation, Object value) { this.key = key; this.operation = operation; this.value = value; } public String getKey() { return key; } public String getOperation() { return operation; } public Object getValue() { return value; } } public class ProductSpecification implements Specification<Product> { private SearchCriteria criteria; public ProductSpecification(SearchCriteria criteria) { this.criteria = criteria; } @Override public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder builder) { return switch (criteria.getOperation()) { case ">" -> builder.greaterThan(root.get(criteria.getKey()), criteria.getValue().toString()); case "<" -> builder.lessThan(root.get(criteria.getKey()), criteria.getValue().toString()); case ":" -> { if (root.get(criteria.getKey()).getJavaType() == String.class) { yield builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%"); } else { yield builder.equal(root.get(criteria.getKey()), criteria.getValue()); } } default -> null; }; } } // Usage example in a service method Specification<Product> spec = Specification.where( new ProductSpecification(new SearchCriteria("name", ":", "phone"))) .and(new ProductSpecification(new SearchCriteria("price", ">", 500))); // Then pass 'spec' to your repository method like findAll(spec)
Make sure your entity fields match the keys used in SearchCriteria.
You can combine multiple specifications with and and or for complex queries.
Use this pattern to keep your query logic clean and easy to maintain.
The Specification pattern helps build flexible database queries step-by-step.
It uses simple criteria objects to define conditions like equals, greater than, or contains.
You can combine multiple specifications to create complex filters easily.