0
0
SpringbootHow-ToBeginner · 4 min read

How to Use Specification in Spring Data for Flexible Queries

Use Specification in Spring Data by implementing its toPredicate method to define query conditions. Then pass the specification to repository methods like findAll(Specification) to execute flexible, dynamic queries.
📐

Syntax

The Specification interface requires implementing the toPredicate method, which builds query conditions using the CriteriaBuilder and Root objects. You then pass the specification to repository methods like findAll(Specification) to fetch filtered results.

  • Root<T>: Represents the entity root in the query.
  • CriteriaQuery<?>: The query being built.
  • CriteriaBuilder: Used to create predicates (conditions).
  • Predicate: Represents a single condition in the query.
java
public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}

// Usage in repository
List<T> findAll(Specification<T> spec);
💻

Example

This example shows how to create a specification to filter Product entities by minimum price and category, then use it with a Spring Data JPA repository.

java
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;

public class ProductSpecifications {
    public static Specification<Product> hasMinPrice(Double minPrice) {
        return (Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            if (minPrice == null) {
                return cb.conjunction(); // no filtering
            }
            return cb.greaterThanOrEqualTo(root.get("price"), minPrice);
        };
    }

    public static Specification<Product> hasCategory(String category) {
        return (root, query, cb) -> {
            if (category == null || category.isEmpty()) {
                return cb.conjunction();
            }
            return cb.equal(root.get("category"), category);
        };
    }
}

// In your service or controller
Specification<Product> spec = Specification.where(ProductSpecifications.hasMinPrice(100.0))
    .and(ProductSpecifications.hasCategory("Electronics"));

List<Product> results = productRepository.findAll(spec);
Output
List of Product objects with price >= 100.0 and category 'Electronics'
⚠️

Common Pitfalls

  • Not handling null values in specifications can cause errors or unwanted filtering.
  • Forgetting to combine specifications with and or or leads to incomplete queries.
  • Using entity field names incorrectly in root.get() causes runtime errors.
  • Returning null instead of cb.conjunction() when no condition is needed breaks the query.
java
/* Wrong: returns null when no filter, causes error */
return (root, query, cb) -> {
    if (minPrice == null) {
        return null; // wrong
    }
    return cb.greaterThanOrEqualTo(root.get("price"), minPrice);
};

/* Right: returns conjunction (always true) when no filter */
return (root, query, cb) -> {
    if (minPrice == null) {
        return cb.conjunction(); // correct
    }
    return cb.greaterThanOrEqualTo(root.get("price"), minPrice);
};
📊

Quick Reference

Use these tips when working with Specification in Spring Data:

  • Implement toPredicate to define query conditions.
  • Use cb.conjunction() to represent no filtering (always true).
  • Combine specifications with and and or methods.
  • Pass the specification to repository methods like findAll.
  • Always use correct entity field names in root.get().

Key Takeaways

Implement Specification by overriding toPredicate to build query conditions.
Use cb.conjunction() to safely handle optional filters without breaking queries.
Combine multiple specifications with and() and or() for flexible queries.
Pass Specification instances to repository methods like findAll() to execute queries.
Always use correct entity field names and handle nulls to avoid runtime errors.