Building Flexible Search APIs in Spring Boot with QueryDSL
Create dynamic and type-safe search functionality using QueryDSL in Spring Boot applications
Most modern applications need flexible search functionality — whether it’s filtering products in an e-commerce site, searching users in a CRM, or querying logs in a dashboard. Static SQL or JPA queries quickly become unmanageable when handling dynamic filters.
QueryDSL offers a powerful, type-safe alternative. With its fluent API, you can construct complex queries dynamically while retaining full compiler-time validation.
In this post, we’ll explore how to integrate QueryDSL with Spring Boot, and how to build dynamic, flexible search APIs that scale with your application.
What is QueryDSL?
QueryDSL is a domain-specific language for querying relational databases in a type-safe way. It generates query types (e.g., QUser
, QProduct
) from your entities and allows for expressive, fluent-style query construction.
Benefits:
- Type safety at compile time
- Clean syntax similar to Java streams
- Support for complex boolean logic, joins, and projections
- Works seamlessly with Spring Data JPA
Adding QueryDSL to Your Spring Boot Project
To get started, add the following dependencies:
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
Then configure your build plugin (for Maven):
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
Generated classes (like QUser
) will appear in target/generated-sources
.
Defining Your Entity
Create a JPA entity for users:
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
private LocalDate birthdate;
}
QueryDSL will generate QUser
for this entity.
Creating a Custom Repository with QueryDSL
Extend QuerydslPredicateExecutor
or create a custom implementation:
public interface UserRepository extends JpaRepository<User, Long>,
QuerydslPredicateExecutor<User> {
}
Now you can use dynamic predicates in your services or controllers.
Building Dynamic Predicates
You can compose filters based on request parameters:
public Predicate buildPredicate(String name, String email) {
QUser user = QUser.user;
BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
builder.and(user.name.containsIgnoreCase(name));
}
if (email != null) {
builder.and(user.email.containsIgnoreCase(email));
}
return builder;
}
Then call it:
public List<User> searchUsers(String name, String email) {
return userRepository.findAll(buildPredicate(name, email));
}
This allows full flexibility without writing custom SQL or JPA queries.
Exposing a Flexible Search API
Create a REST controller:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/search")
public List<User> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) String email) {
QUser user = QUser.user;
BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
builder.and(user.name.containsIgnoreCase(name));
}
if (email != null) {
builder.and(user.email.containsIgnoreCase(email));
}
return (List<User>) userRepository.findAll(builder);
}
}
Call the API like:
GET /api/users/search?name=john&email=gmail
Adding Pagination and Sorting
QueryDSL supports pagination with Spring’s Pageable
interface:
@GetMapping("/search")
public Page<User> search(
@RequestParam(required = false) String name,
@RequestParam(required = false) String email,
Pageable pageable) {
QUser user = QUser.user;
BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
builder.and(user.name.containsIgnoreCase(name));
}
if (email != null) {
builder.and(user.email.containsIgnoreCase(email));
}
return userRepository.findAll(builder, pageable);
}
Advanced Filtering with Query Parameters
You can implement range filters, date filters, or boolean flags:
if (minDate != null) {
builder.and(user.birthdate.goe(minDate));
}
if (maxDate != null) {
builder.and(user.birthdate.loe(maxDate));
}
This makes QueryDSL ideal for building advanced search interfaces or data grids.
Pros and Cons of QueryDSL
Pros:
- Type-safe, fluent syntax
- Eliminates boilerplate query strings
- Dynamic query composition
- Easy integration with Spring Data
Cons:
- Requires build configuration for code generation
- Slight learning curve
- IDE support varies depending on setup
Conclusion
QueryDSL is a powerful tool that dramatically improves how you write dynamic search logic in Spring Boot applications. By leveraging its type-safe API and BooleanBuilder, you can build APIs that adapt to complex filtering requirements — all while maintaining clean, readable code.
For any Java developer working on search-heavy backends, QueryDSL is a must-have in your Spring toolkit.