Composable Predicates: The Specification Pattern

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.

I. The Problem: Method Explosion

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.

II. Type-Safe Composition

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);
    }
}

III. Concrete Example: Dynamic Search with JPA Criteria

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);

IV. Technical Integrity and Use Cases

  1. Validation: Use Specifications to validate domain objects before saving. if (!orderSpecs.canShip().isSatisfiedBy(order)) { throw ... }
  2. Selection: Filter in-memory collections using the same logic used for database queries.
  3. Refactor for Reusability: If a rule like "Is Authoritative" appears in search, sidebar logic, and API filters, it must be a Specification to ensure consistency across the system.
  4. Performance: Be careful with the JPA Criteria API. It is powerful but can generate inefficient SQL if predicates are nested too deeply or if joins are not managed correctly.

See Also: