Specifications
Specifications provide a way to build dynamic, type-safe, reusable queries using the JPA Criteria API . They are ideal for complex filtering scenarios where query conditions are determined at runtime.
Think of Specifications as predicates (conditions) that can be combined using logical operators like AND/OR .
What is a Specification?
A Specification is a predicate builder for an entity. It implements the Specification<T> interface:
Copy
publicinterfaceSpecification<T> {
PredicatetoPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
T → Entity type
toPredicate() → Builds query conditions dynamically
Composable → Can be combined using and(), or(), not()
Why Use Specifications?
Dynamic Querying
Allows building queries at runtime based on available parameters.
Perfect for search filters , where users may provide any combination of criteria.
Type-Safe Queries
Uses Criteria API , so queries are checked at compile time .
Reduces errors caused by string-based JPQL queries.
Reusable Query Logic
Each condition can be encapsulated in a Specification method .
Promotes code reuse across multiple services or modules.
Composable Conditions
Combine multiple specifications with AND / OR operators .
Enables building complex queries without writing many repository methods.
Supports Pagination & Sorting
Works seamlessly with Spring Data’s Pageable and Sort.
Makes large datasets manageable in UI or API responses.
Better Maintainability
Keeps query logic separate from business logic .
Easy to read, modify, and extend without changing repository methods.
Ideal for Search/Filter APIs
Use in scenarios where users provide optional filters .
Avoids creating dozens of repository methods for every combination.
Creating a Simple Specification
Example: Filter users by status:
Copy
publicclassUserSpecifications {
publicstatic Specification<User>hasStatus(String status) {
return (root, query, cb) -> cb.equal(root.get("status"), status);
}
publicstatic Specification<User>isActive() {
return (root, query, cb) -> cb.isTrue(root.get("active"));
}
}
Returns a Specification object encapsulating a condition
Can be reused across multiple queries
Combining Specifications
Specifications can be combined dynamically:
Copy
Specification<User> spec = UserSpecifications.hasStatus("ACTIVE").and(UserSpecifications.ageGreaterThan(25));
List<User> users = userRepository.findAll(spec);
Specification.where() → Starts the chain
and() / or() → Combine multiple conditions dynamically
Enables dynamic search filters for dashboards or admin panels
Repository Integration
To use Specifications, extend JpaSpecificationExecutor<T> along with JpaRepository:
Copy
publicinterfaceUserRepositoryextendsJpaRepository<User, Long>, JpaSpecificationExecutor<User> {}
Methods available:
findAll(Specification<T> spec)
findOne(Specification<T> spec)
count(Specification<T> spec)
Supports pagination and sorting :
Copy
Page<User> page = userRepository.findAll(spec, PageRequest.of(0,10, Sort.by("name")));
Advanced Example: Dynamic Search
Copy
Specification<User> spec = Specification.where(null);
if(name !=null) {
spec = spec.and(UserSpecifications.hasName(name));
}
if(active !=null) {
spec = spec.and(active ? UserSpecifications.isActive() : UserSpecifications.isInactive());
}
List<User> users = userRepository.findAll(spec);
Builds queries conditionally based on input parameters
Avoids creating multiple repository methods for every filter combination
Fluent API Usage (Spring Data 3.5+)
Spring Data JPA provides fluent query methods for advanced execution:
Copy
Page<UserDTO> page = userRepository.findBy(spec,
q -> q.as(UserDTO.class)
.page(PageRequest.of(0,20, Sort.by("lastname")))
);
Optional<User> firstUser = userRepository.findBy(spec,
q -> q.sortBy(Sort.by("createdDate").descending())
.first()
);
Intermediate methods: sortBy(), limit(), as(), project()
Terminal methods: first(), one(), all(), page(), slice(), stream()
Gives fine-grained control over query execution and projections
PredicateSpecification / UpdateSpecification / DeleteSpecification
PredicateSpecification: A flexible, query-agnostic predicate interface
UpdateSpecification / DeleteSpecification: Specialized specifications for update or delete operations
Copy
publicstatic UpdateSpecification<User>updateStatusById(String status, Long id) {
return UpdateSpecification.<User>update((root, update, cb) -> update.set("status", status)).where(UserSpecifications.hasId(id));
}
Best Practices
Keep each Specification focused on a single condition
Combine Specifications in the service layer for complex filters
Avoid putting business logic inside Specifications
Use DTO projections for read-only queries to improve performance
Real-World Use Cases
Advanced search screens with multiple optional filters
Filterable dashboards
Reporting modules requiring aggregation or conditional queries
Admin panels with flexible search parameters
E-commerce product filtering with multiple dynamic options
Conclusion
Specifications are dynamic, reusable, and type-safe query predicates
Built on Criteria API
Combine multiple filters using AND/OR
Integrates with JpaSpecificationExecutor for repository support
Ideal for search forms, dashboards, dynamic filters, and reporting modules