0
0
Spring Bootframework~5 mins

Specification pattern for dynamic queries in Spring Boot

Choose your learning style9 modes available
Introduction

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.

When you want to filter database results based on user input that can change often.
When you need to combine multiple search conditions dynamically.
When you want to keep your query logic clean and separated from your main code.
When you want to reuse query conditions in different parts of your app.
When you want to avoid writing many custom query methods for every filter combination.
Syntax
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.

Examples
This example filters products by a string field using 'like' for partial matches.
Spring Boot
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;
    }
}
This combines two conditions: price greater than 100 and category equals 'Books'.
Spring Boot
Specification<Product> spec = Specification.where(new ProductSpecification(new SearchCriteria("price", ">", 100)))
    .and(new ProductSpecification(new SearchCriteria("category", ":", "Books")));
Sample Program

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.

Spring Boot
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)
OutputSuccess
Important Notes

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.

Summary

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.