Query Filter Builder is a robust and versatile library designed to dynamically generate type-safe JPA queries from HTTP query parameters and request bodies in Spring Boot applications.
- Overview
- Features
- Requirements
- Installation
- Configuration
- Quick Start
- Usage
- Supported Operators
- Supported Types
- Advanced Features
- Edge Cases & Error Handling
- Examples
- DTOs for Consistent API Design
- Public API Reference
- Contributing
- License
Query Filter Builder simplifies the process of dynamically filtering and sorting data in Spring Boot applications by converting HTTP query parameters and JSON request bodies into type-safe JPA predicates. It eliminates the complexity of manually writing filtering logic while maintaining clean and maintainable code. By abstracting query construction, it enables you to deliver clean software in less time, accelerating development and reducing maintenance overhead.
- Automatic Conversion: Effortlessly transform query parameters and request bodies into JPA predicates.
- Comprehensive Operators: Supports +18 filtering operators including comparison, collection, string matching, null checks, and range operators.
- Type-Safe Validation: Ensures data integrity with robust parameter validation and type conversion.
- Clean API Design: Provides an intuitive and developer-friendly API with fluent interfaces.
- Spring Data Integration: Seamlessly works with Spring Data JPA and Hibernate.
- Nested Property Filtering: Enables filtering across related entities with customizable field delimiters.
- Extensibility: Highly customizable with custom filter functions, custom sort functions, and expression providers.
- Field Aliases: Allows clients to use stable, concise, or more user-friendly names in API requests.
- Flexible Configuration: Configurable field delimiters, sort parameters, and localization support.
- Custom Filters & Sorting: Support for complex, custom filtering and sorting logic beyond standard operators.
- Registry-Based Architecture: Extensible operator and field registries for maximum flexibility.
- Comprehensive Testing: Extensive unit test coverage for all operators, data types, and edge cases.
- Security-First Design: Input validation, SQL injection prevention, and secure type conversion.
- Performance Optimized: Efficient query building with minimal overhead and smart predicate generation.
- Request Body Support: Full support for JSON request body filtering and sorting alongside query parameters.
- Java: Version 17 or higher
- Spring Boot: Version 3.1.0 or higher
- Jakarta Persistence API: Version 3.1.0 or higher
To integrate Query Filter Builder into your project, add the following Maven dependency:
<dependency>
<groupId>io.github.0xorigin</groupId>
<artifactId>query-filter-builder</artifactId>
<version>2.1.0</version>
</dependency>- The default field delimiter for nested property paths is
.(dot). You can change it by setting the propertyquery-filter-builder.defaults.field-delimiterin your application configuration.- Note: If you change the field delimiter, make sure to use the same delimiter in your filter and sort context template definitions (e.g., when specifying nested fields like
createdBy.firstName).
- Note: If you change the field delimiter, make sure to use the same delimiter in your filter and sort context template definitions (e.g., when specifying nested fields like
- The default sort parameter name used for sorting via HTTP query parameters is
sort. You can change it by setting the propertyquery-filter-builder.query-param.defaults.sort-parameterin your application configuration.
- Inject
QueryFilterBuilder<T>: Inject an instance ofQueryFilterBuilder<T>into your class—typically a service layer class—whereTis your entity class. - Define a Template: Use
FilterContext.buildTemplateForTypeandSortContext.buildTemplateForTypeto specify which fields are filterable/sortable and how. - Build a Context: Use the template's
newSourceBuilder()to create aSourceBuilder, then provide the input source (query or body) and callbuildFilterContext()orbuildSortContext(). - Build Specification: Use your injected
QueryFilterBuilderinstance to build a JPASpecificationfor filtering and sorting. - Apply to Repository: Ensure your repository extends
JpaSpecificationExecutorand pass the specification to itsfindAllmethod.
First, inject an instance of QueryFilterBuilder<T> into your desired class, typically a service. T should be your JPA entity.
import io.github._0xorigin.queryfilterbuilder.QueryFilterBuilder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final QueryFilterBuilder<User> queryFilterBuilder;
public UserService(QueryFilterBuilder<User> queryFilterBuilder) {
this.queryFilterBuilder = queryFilterBuilder;
}
// ... your service logic
}Define templates at application startup (e.g., in a private final field) to encapsulate allowed fields, operators, and custom logic for both filters and sorts. Use the builder pattern to configure templates for each entity.
// 1. Build and store the template (typically at startup in a private final field)
FilterContext.Template<User> userFilterTemplate = FilterContext.buildTemplateForType(User.class)
.queryParam(configurer -> configurer
.addFilter("role", Operator.EQ, Operator.NEQ, Operator.IN)
.addFilter(Set.of("first-name", "name"), "firstName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS) // with set of aliases
.addFilter("lastName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
.addFilter("isActive", (root, cq, cb) -> root.get("isActive"), Operator.EQ) // Can provide expression
.addFilter("createdAt", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
)
.requestBody(configurer -> configurer
.addFilter("role", Operator.EQ, Operator.NEQ, Operator.IN)
.addFilter("firstName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
.addFilter("lastName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
.addFilter("active", "isActive", Operator.EQ) // with alias
.addFilter("lastLogin", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
.addFilter("createdAt", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
.addFilter("createdBy.firstName", Operator.EQ) // Nested field example
.addCustomFilter("customRoleFilter", User.Role.class,
(root, criteriaQuery, cb, values, filterErrorWrapper) ->
Optional.of(cb.equal(root.get("role"), values.get(0)))
)
)
.buildTemplate();
SortContext.Template<User> userSortTemplate = SortContext.buildTemplateForType(User.class)
.queryParam(configurer -> configurer
.addSorts("firstName") // Adds both ASC and DESC
.addSorts("lastName")
.addSorts("createdAt", (root, cq, cb) -> root.get("createdAt")) // Can provide expression
.addSorts("role")
.addDescSort("createdBy.firstName") // Nested field sort
.addAscSort("createdBy.lastName")
.addCustomSort("customSort",
(root, cq, cb, errorWrapper) ->
Optional.of(cb.asc(root.get("firstName")))
)
)
.requestBody(configurer -> configurer
.addSorts(Set.of("first-name", "name"), "firstName") // with set of aliases
.addSorts("lastName")
.addSorts("createdDateTime", "createdAt") // with alias
.addSorts("role"))
.buildTemplate();The library supports registering client-facing aliases for entity fields. Aliases allow you to expose stable, concise, or more user-friendly names in your API while mapping them to the actual entity field names used by the JPA code.
- When you call the convenience methods like
addFilter(fieldName, ...)oraddSort(fieldName, ...), the template will register a default alias where the alias equals the entity field name. This preserves backwards compatibility for clients that already use entity field names directly. - To explicitly register an alias use the overloads that accept an alias (or a set of aliases). The alias is what clients will include in query parameters or request bodies; the library will map that alias to the configured entity field.
- The final built
FilterContext/SortContextexposes the alias-to-field mapping viagetAliasToFieldMap()so you can inspect or document what aliases are available.
Example — register aliases for filters and sorts:
// register a single alias 'givenName' that maps to entity field 'firstName'
userFilterTemplate = FilterContext.buildTemplateForType(User.class)
.requestBody(cfg -> cfg.addFilter("givenName", "firstName", Operator.EQ));
// register multiple aliases for a sort: 'name' and 'fullname' map to 'firstName'
userSortTemplate = SortContext.buildTemplateForType(User.class)
.queryParam(cfg -> cfg.addSort(Set.of("name", "fullname"), "firstName"));
Note: aliases must be unique across the template — attempting to register the same alias for different fields will result in an IllegalArgumentException at template build time.
// For query parameters (e.g., from HttpServletRequest)
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
.withQuerySource(request)
.buildFilterContext();
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
.withQuerySource(request)
.buildSortContext();
// For request body (e.g., from controller DTOs)
FilterContext<User> filterContextFromBody = userFilterTemplate.newSourceBuilder()
.withBodySource(filterRequests)
.buildFilterContext();
SortContext<User> sortContextFromBody = userSortTemplate.newSourceBuilder()
.withBodySource(sortRequests)
.buildSortContext();
// Providing both query parameters and request body
FilterContext<User> filterContextWithBoth = userFilterTemplate.newSourceBuilder()
.withQuerySource(request)
.withBodySource(filterRequests)
.buildFilterContext();
SortContext<User> sortContextWithBoth = userSortTemplate.newSourceBuilder()
.withQuerySource(request)
.withBodySource(sortRequests)
.buildSortContext();Specification<User> filterSpec = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<User> sortSpec = queryFilterBuilder.buildSortSpecification(sortContext);
// Combine and use with Spring Data JPA
List<User> users = userRepository.findAll(Specification.allOf(filterSpec, sortSpec));// Filtering on nested property (e.g., createdBy.firstName)
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
.withQuerySource(request)
.withBodySource(filterRequests)
.buildFilterContext();
// Example query param: `?createdBy.firstName.contains=John`
// Example request body: `[{"field": "createdBy.firstName", "operator": "contains", "value": "John"}]`
// Sorting on nested property (e.g., createdBy.lastName DESC)
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
.withQuerySource(request)
.withBodySource(sortRequests)
.buildSortContext();
// Example query param: `?sort=-createdBy.lastName`
// Example request body: `[{"field": "createdBy.lastName", "direction": "ASC"}]`When resolving nested fields that were declared using the template's normal field methods (i.e. fields added by name), and not fields that use an expression provider or a custom filter/sort function, the library will reuse association fetches that already exist on the query root. In practical terms this means:
- If your code (or a custom Specification) adds a fetch/association on the root before the package builds its own specification, the library will detect and reuse those fetches rather than creating duplicate joins.
- If you supply your own Specification or pre-configured context and apply it before the specification generated by this package, any fetches you added can be reused by the generated specification. This ordering can lead to behavior that looks unexpected if you assume the generated specification will be the first to configure associations.
Guidance to avoid surprises:
- Be intentional about the order in which you apply custom specifications and the package-generated specification. If you want the package to create its own associations, avoid pre-applying custom specs that add fetches.
- If you need custom fetches to be used by the generated specification, add them deliberately (for example in your template or custom specification) before combining specifications.
- Keep specifications small and well-documented: clearly document any custom fetches so other developers understand the ordering and reuse behavior.
This behavior is designed to avoid duplicate joins and to respect user-provided query modifications, but it can be surprising if you are not expecting fetches from previously-applied specifications to be reused.
If you want the generated specification to reuse a fetch you added, apply a small Specification that performs the fetch before combining it with the generated specs. For example:
Specification<Book> fetches = (root, cq, cb) -> {
root.fetch("author", JoinType.LEFT);
return null;
};
Specification<Book> filterSpecs = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<Book> sortSpecs = queryFilterBuilder.buildSortSpecification(sortContext);
Page<Book> books = bookRepository.findAll(Specification.allOf(fetches, filterSpecs, sortSpecs), pageable);Note: if the fetch is created with JoinType.LEFT or JoinType.RIGHT, the package will reuse that fetch as-is (the same join type will be respected).
You can use the provided ListAPIRequest DTO to accept filtering and sorting criteria in a structured way, making your API endpoints consistent and predictable. Below is a complete example of a controller and service using ListAPIRequest:
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/list")
public ResponseEntity<List<User>> listUsers(@Valid @RequestBody ListAPIRequest request) {
List<User> users = userService.listUsers(request);
return ResponseEntity.ok(users);
}
}import java.util.Objects;
@Service
public class UserService {
private final QueryFilterBuilder<User> queryFilterBuilder;
private final UserRepository userRepository;
private final FilterContext.Template<User> userFilterTemplate;
private final SortContext.Template<User> userSortTemplate;
public UserService(QueryFilterBuilder<User> queryFilterBuilder,
UserRepository userRepository) {
this.queryFilterBuilder = queryFilterBuilder;
this.userRepository = userRepository;
// templates initialized elsewhere (in real code these would be provided/injected)
}
public List<User> listUsers(ListAPIRequest request) {
Objects.requireNonNull(userFilterTemplate, "userFilterTemplate must be initialized");
Objects.requireNonNull(userSortTemplate, "userSortTemplate must be initialized");
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
.withBodySource(request.filters())
.buildFilterContext();
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
.withBodySource(request.sorts())
.buildSortContext();
Specification<User> filterSpecification = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<User> sortSpecification = queryFilterBuilder.buildSortSpecification(sortContext);
return userRepository.findAll(Specification.allOf(filterSpecification, sortSpecification));
}
}This approach ensures maintainable, consistent, and predictable results for your API endpoints.
Operator literals are case-insensitive: whether sent in query parameters or request body, any case (e.g., EQ, eq, Eq, eQ) will be accepted and correctly interpreted.
| Operator | Description |
|---|---|
eq |
Equal to |
neq |
Not equal to |
gt |
Greater than |
gte |
Greater than or equal to |
lt |
Less than |
lte |
Less than or equal to |
in |
In a list of values |
notIn |
Not in a list of values |
isNull |
Is null |
isNotNull |
Is not null |
contains |
Contains string |
icontains |
Case-insensitive contains |
startsWith |
Starts with |
istartsWith |
Case-insensitive starts with |
endsWith |
Ends with |
iendsWith |
Case-insensitive ends with |
between |
Between two values |
notBetween |
Not between two values |
Query Filter Builder supports a wide range of types for filtering and sorting, classified as follows:
Byte,Short,Integer,Long,Float,Double,BigDecimal,BigInteger,Boolean
Character,String, Any Javaenumtype (enums are treated as text for filtering and sorting)
UUID
Instant,OffsetDateTime,ZonedDateTime,OffsetTime,LocalDateTime,LocalDate,LocalTime,YearMonth,Year
Custom filters allow for complex logic beyond standard operators. Define them in the template using addCustomFilter.
See the Defining Filter and Sort Templates example for usage.
Custom sorts allow for complex logic beyond simple sorting. Define them in the template using addCustomSort.
See the Defining Filter and Sort Templates example for usage.
Query Filter Builder provides a default implementation for enum filtering via EnumFilterFieldImp, which uses Enum.valueOf(Class, String) for casting string values to enum types. This is suitable for most use cases where enum names are matched exactly.
If you need to customize how enum values are parsed (e.g., case-insensitive matching, mapping from other string representations), you can provide your own implementation by subclassing AbstractEnumFilterField and overriding the cast(Class<T>, String) method.
To use your custom implementation, annotate your bean with @Primary so that Spring will inject it instead of the default:
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Primary
@Component
public class MyCustomEnumFilterField extends AbstractEnumFilterField {
@Override
public <T extends Enum<T>> T cast(Class<T> enumClass, String value) {
// Custom logic, e.g., case-insensitive
for (T constant : enumClass.getEnumConstants()) {
if (constant.name().equalsIgnoreCase(value)) {
return constant;
}
}
throw new IllegalArgumentException("No enum constant " + enumClass.getName() + "." + value);
}
}Spring will automatically use your implementation for enum filtering if it is marked as @Primary.
When sorting via HTTP query parameters, the sort parameter defines the fields and their sorting order. The format is as follows:
sort=-field1,field2,field3,...
- A leading
-before a field name indicates descending order. - If no
-is present, the field is sorted in ascending order. - Multiple fields can be specified, separated by commas. The sorting respects the order of fields as they appear in the query parameter.
sort=-firstName,lastName,createdAt
firstNamewill be sorted in descending order.lastNamewill be sorted in ascending order.createdAtwill be sorted in ascending order.
- The order of fields in the request body determines the sorting precedence.
- Sorting direction literals are case-insensitive. Any case (e.g., ASC, asc, aSc, DESC, desc, dEsC) will be accepted and correctly interpreted.
- Defaults to ASC if no direction is specified or if the direction is invalid string.
This ensures that the sorting process is predictable and respects the client's specified order, whether provided in query parameters or the request body.
- Request Body Override: If a filter or sort field is provided in both query parameters and the request body, the value from the request body will take precedence and override the one from the query parameters.
- If a field passed via query parameters or request body is not defined in the filter/sort template, it is silently ignored.
- If the entity class is not a JPA
@Entity, an exception is thrown. - If the context or required arguments are null, a
NullPointerExceptionis thrown. - If no valid filters or sorts are found, the resulting specification will not filter or sort any results.
- Unsupported types will throw an error at template build time.
- In all other error scenarios, an exception is thrown to ensure predictable behavior and easier debugging. This includes:
- Invalid value for a field (e.g., "abc" for a number).
- Invalid operator for a field.
- Invalid field path for nested properties.
- Misconfiguration of templates.
- The specific exceptions thrown are detailed in the
Exception Handlingsection.
Query Filter Builder provides robust exception handling to ensure predictable error responses and easier debugging.
- The package provides a base exception handler class (
QueryFilterBuilderExceptionHandler). To activate exception handling, you must subclass this base class in your application and annotate it with@ControllerAdvice. This enables automatic handling of exceptions thrown by the library and returns meaningful error responses in your Spring Boot application. - You can further customize the error handling by overriding methods in your subclass.
- Description: Thrown when a query parameter or request body contains invalid, unrecognized, or unconvertible values (e.g., invalid value).
- Handling: The exception handler will return a clear error message indicating which parameter or value was invalid, helping clients correct their requests.
- Description: Thrown for internal errors within the filter builder, such as misconfiguration, template errors, or unexpected failures during query construction.
- Handling: The exception handler will return a clear error message indicating which parameter or value was invalid, helping developers correct their templates.
For custom error handling, you can provide your own @RestControllerAdvice and handle these exceptions as needed.
// Assume userFilterTemplate and userSortTemplate are defined as above
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
.withQuerySource(request)
.buildFilterContext();
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
.withQuerySource(request)
.buildSortContext();
Specification<User> filterSpec = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<User> sortSpec = queryFilterBuilder.buildSortSpecification(sortContext);
List<User> users = userRepository.findAll(filterSpec.and(sortSpec));// Example: Filter users by their creator's first name
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
.withQuerySource(request)
.buildFilterContext();
// Query param: ?createdBy.firstName=John// Example: Sort users by their creator's last name descending
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
.withQuerySource(request)
.buildSortContext();
// Query param: ?sort=-createdBy.lastNameThe package provides a convenient ListAPIRequest DTO, which encapsulates lists of FilterRequest and SortRequest objects. This DTO is ready to use in any controller method, allowing you to accept filtering and sorting criteria in a structured and predictable way:
import jakarta.validation.Valid;
@PostMapping("/users/list")
public ResponseEntity<List<User>> listUsers(@Valid @RequestBody ListAPIRequest request) {
Specification<User> filterSpecs = queryFilterBuilder.buildFilterSpecification(
userFilterTemplate.newSourceBuilder().withBodySource(request.filters()).buildFilterContext()
);
Specification<User> sortSpecs = queryFilterBuilder.buildSortSpecification(
userSortTemplate.newSourceBuilder().withBodySource(request.sorts()).buildSortContext()
);
List<User> users = userRepository.findAll(Specification.allOf(filterSpecs, sortSpecs));
return ResponseEntity.ok(users);
}Using ListAPIRequest helps standardize your API endpoints, making development more consistent and results more predictable. It also improves maintainability by providing a single DTO for both filtering and sorting operations.
Static Entry Point
buildTemplateForType(Class<T> type): Creates a TemplateBuilder for the given entity type. This is the entry point for defining a filter template. The class must be annotated with @Entity.
TemplateBuilder Methods
queryParam(Consumer<FilterConfigurer<T>> configurer): Configures filters that are sourced from query parameters. Accepts a consumer that provides a FilterConfigurer to define the filters.requestBody(Consumer<FilterConfigurer<T>> configurer): Configures filters that are sourced from the request body. Accepts a consumer that provides a FilterConfigurer to define the filters.buildTemplate(): Builds the final immutable Template instance containing all filter configurations.
Template Methods
newSourceBuilder(): Creates a new SourceBuilder from this template, which can then be used to specify the source of the filter data (query parameters or request body).
SourceBuilder Methods
withQuerySource(HttpServletRequest request): Specifies that the filter data will be sourced from an HttpServletRequest (i.e., query parameters).withBodySource(List<FilterRequest> filterRequests): Specifies that the filter data will be sourced from a list of FilterRequest objects (i.e., request body).buildFilterContext(): Builds the final FilterContext instance containing the template configuration and the data source.
FilterConfigurer Methods
addFilter(String fieldName, Operator... operators): Adds a filter for the specified field with the given operators. The field name is used as its default alias for backward compatibility.addFilter(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter with a custom expression provider. Useful when filter logic requires more than simple field comparison. The field name is used as default alias.addFilter(String alias, String fieldName, Operator... operators): Adds a filter for the given entity field and registers a single client-facing alias for it. The alias is what clients will use when submitting filter parameters.addFilter(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter backed by a custom expression provider and registers a single client-facing alias for it.addFilter(Set<String> aliases, String fieldName, Operator... operators): Adds a filter for the given entity field and registers multiple client-facing aliases that map to it. All aliases will be associated with the same field.addFilter(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter that uses a custom expression provider and registers multiple client-facing aliases for it.addCustomFilter(String filterName, Class<K> dataTypeForInput, CustomFilterFunction<T> filterFunction): Adds a custom filter with a specific name, data type, and filter function. Custom filters allow for complex filtering logic that cannot be expressed with standard operators.
Static Entry Point
buildTemplateForType(Class<T> type): Creates a TemplateBuilder for the given entity type. This is the entry point for defining a sort template. The class must be annotated with @Entity.
TemplateBuilder Methods
queryParam(Consumer<SortConfigurer<T>> configurer): Configures sorts that are sourced from query parameters. Accepts a consumer that provides a SortConfigurer to define the sorts.requestBody(Consumer<SortConfigurer<T>> configurer): Configures sorts that are sourced from the request body. Accepts a consumer that provides a SortConfigurer to define the sorts.buildTemplate(): Builds the final immutable Template instance containing all sort configurations.
Template Methods
newSourceBuilder(): Creates a new SourceBuilder from this template, which can then be used to specify the source of the sort data (query parameters or request body).
SourceBuilder Methods
withQuerySource(HttpServletRequest request): Specifies that the sort data will be sourced from an HttpServletRequest (i.e., query parameters).withBodySource(List<SortRequest> sortRequests): Specifies that the sort data will be sourced from a list of SortRequest objects (i.e., request body).buildSortContext(): Builds the final SortContext instance containing the template configuration and the data source.
SortConfigurer Methods - Field Name Based (No Alias)
addAscSort(String fieldName): Adds an ascending sort for the specified field. Registers a default alias where the alias equals the field name for backward compatibility.addDescSort(String fieldName): Adds a descending sort for the specified field. Registers a default alias where the alias equals the field name.addSorts(String fieldName): Adds both ascending and descending sort options for the specified field. Registers a default alias where the alias equals the field name.addAscSort(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds an ascending sort with a custom expression provider. Useful when sort logic requires more than simple field comparison. Registers a default alias.addDescSort(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds a descending sort with a custom expression provider. Registers a default alias.addSorts(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds both ascending and descending sort options with a custom expression provider. Registers a default alias.
SortConfigurer Methods - Single Alias
addAscSort(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows ascending sorting for that field.addDescSort(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows descending sorting for that field.addSorts(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows both ascending and descending sorting for that field.addAscSort(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute the ascending sort expression.addDescSort(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute the descending sort expression.addSorts(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute both ascending and descending sort expressions.
SortConfigurer Methods - Multiple Aliases
addAscSort(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow ascending sorting for that field.addDescSort(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow descending sorting for that field.addSorts(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow both ascending and descending sorting for that field.addAscSort(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute the ascending sort expression.addDescSort(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute the descending sort expression.addSorts(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute both ascending and descending sort expressions.
SortConfigurer Methods - Custom Sort
addCustomSort(String sortName, CustomSortFunction<T> sortFunction): Adds a custom sort with a specific name and sort function. Custom sorts allow for complex sorting logic that cannot be expressed with standard field-based sorting.
buildFilterSpecification(FilterContext<T> filterContext): Builds a JPA Specification for filtering based on the provided FilterContext.buildSortSpecification(SortContext<T> sortContext): Builds a JPA Specification for sorting based on the provided SortContext.
For more advanced usage, see the integration tests and Javadoc.
We welcome contributions from the community to make Query Filter Builder even better! Please refer to the CONTRIBUTING.md file for detailed guidelines on how to contribute to the project.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.