The Specification Pattern is used to encapsulate a business rule as a single, reusable boolean predicate. These specifications can be combined using logical operators (AND, OR, NOT) to build complex, dynamic business rules or database queries.
Without specifications, repositories often end up with a "Method Explosion":
findByStatus, findByStatusAndType, findByStatusAndTypeAndDate...
The Specification pattern solves this by allowing the client to pass a single, composed predicate to a generic findAll(Specification spec) method.
Specifications are essentially objects that implement a "matches" or "isSatisfiedBy" method.
public interface Specification<T> {
boolean isSatisfiedBy(T candidate);
default Specification<T> and(Specification<T> other) {
return candidate -> this.isSatisfiedBy(candidate) && other.isSatisfiedBy(candidate);
}
}
In a Spring Data environment, we use org.springframework.data.jpa.domain.Specification to map domain rules directly to SQL.
public class PageSpecs {
public static Specification<WikiPage> isType(String type) {
return (root, query, cb) -> cb.equal(root.get("type"), type);
}
public static Specification<WikiPage> inCluster(String cluster) {
return (root, query, cb) -> cb.equal(root.get("cluster"), cluster);
}
}
// Usage in Service
Specification<WikiPage> search = PageSpecs.isType("article")
.and(PageSpecs.inCluster("java"));
List<WikiPage> results = repository.findAll(search);
if (!orderSpecs.canShip().isSatisfiedBy(order)) { throw ... }See Also: