Building Dynamic Queries in Spring Boot with JPASpecificationExecutor
Crafting efficient and flexible queries is at the heart of every robust backend application. While static queries suffice in many scenarios, there are moments when dynamic queries are the cornerstone of achieving clean and maintainable code, espe...

- Build complex queries programmatically.
- Use a clean, type-safe API.
- Combine multiple query conditions dynamically.
- Support pagination and sorting effortlessly.
public interface ProductRepository extends JpaRepository<Product, Long>, JPASpecificationExecutor<Product> {
}
import org.springframework.data.jpa.domain.Specification;
public class ProductSpecifications {
public static Specification<Product> hasName(String name) {
return (root, query, criteriaBuilder) ->
name != null ? criteriaBuilder.like(root.get("name"), "%" + name + "%") : null;
}
public static Specification<Product> hasPriceGreaterThan(Double price) {
return (root, query, criteriaBuilder) ->
price != null ? criteriaBuilder.greaterThan(root.get("price"), price) : null;
}
}
- root: Represents the entity being queried.
- query: Helps to modify query behavior.
- criteriaBuilder: Provides methods for constructing query criteria.
import org.springframework.data.jpa.domain.Specification;
Specification<Product> spec = Specification.where(ProductSpecifications.hasName("Laptop"))
.and(ProductSpecifications.hasPriceGreaterThan(1000.0));
List<Product> results = productRepository.findAll(spec);
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("price").descending());
Page<Product> paginatedResults = productRepository.findAll(spec, pageRequest);
public Specification<Product> buildDynamicSpecification(ProductFilter filter) {
return Specification.where(ProductSpecifications.hasName(filter.getName()))
.and(ProductSpecifications.hasPriceGreaterThan(filter.getMinPrice()))
.and(ProductSpecifications.hasPriceLessThan(filter.getMaxPrice()))
.and(ProductSpecifications.belongsToCategory(filter.getCategory()));
}
- Use indexed fields for filters.
- Minimize joins by structuring your entities efficiently.
- Use pagination for large result sets to avoid memory overflows.
public Page<Product> searchProducts(ProductFilter filter, String sortBy, String direction, int page, int size) {
Specification<Product> spec = buildDynamicSpecification(filter);
Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
PageRequest pageRequest = PageRequest.of(page, size, sort);
return productRepository.findAll(spec, pageRequest);
}
public List<ProductDTO> mapToDTO(List<Product> products) {
return products.stream()
.map(product -> new ProductDTO(product.getName(), product.getPrice()))
.collect(Collectors.toList());
}
@Test
public void testHasNameSpecification() {
Specification<Product> spec = ProductSpecifications.hasName("Laptop");
List<Product> results = productRepository.findAll(spec);
assertEquals(1, results.size());
assertEquals("Laptop", results.get(0).getName());
}
Read more at : Building Dynamic Queries in Spring Boot with JPASpecificationExecutor
About the author
I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.
Comments
Join the discussionNo comments yet. Be the first to comment.





