Anıl Şenocak

If you’ve ever written queries like then you already know the pain.
WHERE (:name IS NULL OR name LIKE %:name%)
AND (:status IS NULL OR status = :status)
AND (:date IS NULL OR created\_at > :date)
This is exactly where a Specification Builder approach shines.
In this article, we’ll explore how a Spring Boot Specification Builder project simplifies dynamic querying, eliminates boilerplate, and provides a clean, scalable architecture.
Spring Data JPA offers multiple ways to query:
findByNameAndStatus)Among these, Specifications stand out for dynamic querying because they allow you to build predicates programmatically.
However, raw Specifications come with their own problems:
The Specification Builder project introduces a generic, reusable, and extensible way to construct dynamic queries. Instead of writing custom specifications for every entity, it allows:
✅ Generic filtering
✅ Operator-based querying (EQUAL, LIKE, GREATER_THAN, etc.)
✅ Dynamic field selection
✅ Clean API request structure
✅ Reusable and composable logic
In short: you define filters → builder generates the query
One of the most tedious parts of JPA Specifications is managing joins for nested entities. The library handles this automatically using dot notation.
// This automatically creates all necessary LEFT JOINs for address and city
Specification<User> spec = SpecBuilder.forClass(User.class)
.eq("address.city.name", "New York")
.build();
Dynamic search forms often send null values for optional filters. Instead of wrapping every condition in an if (value != null) block, Easy Specification does it for you. If a value is null, the condition is simply skipped.
String email \= null; // This will be automatically ignored
Specification<User> spec = SpecBuilder.forClass(User.class)
.eq("email", email)
.contains("name", "John")
.build();
Building a dynamic search endpoint? You can easily map your incoming filters to the SpecBuilder.
@PostMapping("/search")
public List<User> search(@RequestBody SearchRequest request) {
SpecBuilder<User> specBuilder = SpecBuilder.forClass(User.class);
for (FilterDto filter : request.filters()) {
// Map your dynamic filters here...
specBuilder.eq(filter.field(), filter.value());
}
return userRepository.findAll(specBuilder.build());
}
Add the dependency to your pom.xml:
<dependency>
<groupId>com.github.senocak.easyspec</groupId>
<artifactId>spring-boot-easy-specification</artifactId>
<version>1.0.1</version>
</dependency>
Or for Gradle:
implementation 'com.github.senocak.easyspec:spring-boot-easy-specification:1.0.1'
import com.github.senocak.easyspec.builder.SpecBuilder;
import com.github.senocak.easyspec.builder.SpecBuilder;
Specification<User> spec = SpecBuilder.forClass(User.class)
.eq("status", UserStatus.ACTIVE)
.orderBy("createdAt", Sort.Direction.DESC)
.build();
List<User> users = userRepository.findAll(spec);
The SpecBuilder supports all the operations you'd expect:
eq, ne, greaterThan, lessThan, gte, lte, between, in.isNull, isNotNull.contains, startsWith, endsWith.orderBy.Happy coding!