This Java library directly converts HTTP query parameters into Hibernate predicates, making it easier to implement dynamic filters for APIs.
- Java 21 or later.
- Spring boot 3.X for application configuration.
- spring-boot-starter-data-jpa for entity management and filtering predicates.
Maven
<dependency>
<groupId>io.github.zorin95670</groupId>
<artifactId>spring-query-filter</artifactId>
<version>1.1.3</version>
</dependency>Gradle
dependencies {
implementation("io.github.zorin95670:spring-query-filter:1.1.3")
}Use default pagination from Spring.
The following query parameters manage pagination options:
page: Integer starting at 0, representing the desired page.size: Integer from 1 to 100, representing the number of elements per page. Defaults to 10.sort: Field name to sort by.direction: Sort direction, either asc (ascending) or desc (descending). Defaults to descending.
Example:
http://localhost:8080/myEndpoint?page=2&pageSize=7&order=name&sort=asc
You can use sort parameter for multiples sort:
Example:
http://localhost:8080/myEndpoint?sort=name,asc&sort=price,desc
Here’s an improved version of your documentation section in English:
The library uses Spring's default pagination mechanism to manage paginated responses.
You can control pagination through the following query parameters:
page: Specifies the zero-based page index to retrieve. Defaults to0if not provided.size: Specifies the number of elements per page. Must be between1and100, with a default of10.sort: Specifies the field(s) by which to sort the results.direction: Specifies the sorting direction. Acceptable values areasc(ascending) ordesc(descending). Defaults todesc.
By default, filtering dates will use a timestamp. However, you can specify a custom date format by including the dateFormat parameter in your request.
Example Request:
GET http://localhost:8080/myEndpoint?dateFormat=yyyyMMdd&date=20241201Notes:
- The
dateFormatparameter defines the expected format of thedatevalue in the request. - For valid date format patterns, refer to the Java DateFormat documentation.
Examples of Supported Formats:
yyyyMMdd→20241201MM/dd/yyyy→12/01/2024dd-MM-yyyy→01-12-2024
Make sure the date value matches the specified dateFormat to avoid parsing errors.
Single Sort
To fetch the second page with 7 elements per page, sorted by name in ascending order:
GET http://localhost:8080/myEndpoint?page=1&size=7&sort=name,asc
Multiple Sort Criteria
To sort by multiple fields, chain sort parameters. For example, to sort by name in ascending order and then by price in descending order:
GET http://localhost:8080/myEndpoint?sort=name,asc&sort=price,desc
Here is the list of operator that can be used to filter data:
eq_or: equalsgt_: greater thanlt_: lesser than_bt_: betweenlk_: like, with*or%as a wildcard equivalent to SQL%not_: negation|: or
| Type | eq_ |
gt_ |
lt_ |
_bt_ |
lk_ |
|---|---|---|---|---|---|
| Boolean | ✅ | ❌ | ❌ | ❌ | ❌ |
| UUID | ✅ | ❌ | ❌ | ❌ | ❌ |
| String | ✅ | ❌ | ❌ | ❌ | ✅ |
| Integer | ✅ | ✅ | ✅ | ✅ | ❌ |
| Long | ✅ | ✅ | ✅ | ✅ | ❌ |
| Float | ✅ | ✅ | ✅ | ✅ | ❌ |
| Double | ✅ | ✅ | ✅ | ✅ | ❌ |
| Date | ✅ | ✅ | ✅ | ✅ | ❌ |
Example:
http://localhost:8080/myEndpoint?name=toto&age=gt_10&age=lt_20&updateDate=1_bt_5
This query filters YourEntity where:
nameis equal tototo.ageis greater than 10 and less than 20.updateDateis between 1 and 5 (timestamps assumed, simplified here as integers).
Adding multiple query parameters combines filters with an SQL AND.
For a single field, you can specify an OR filter like this: ?name=toto|tata, meaning name should be either toto or tata.
To mix AND and OR:
?name=tata&name=toto|tutu
This corresponds to the SQL:
SELECT * FROM you_entity_table WHERE name = 'tata' AND (name = 'toto' OR name = 'tutu');To negate a filter, use the not_ prefix. For OR filters, apply not_ to each value.
?name=not_test: name is nottest?name=not_lk_test*: name does not matchtest*?name=not_toto&name=not_tata: name is neithertotonortata
This library provides default filters for:
StringDateIntegerLongFloatDoubleBooleanUUID
To enable filtering on a field, annotate it with @FilterType.
Example Entity:
import io.github.zorin95670.predicate.FilterType;
(...)
@Entity
@Table(name = "your_entity_table")
public class YourEntity {
@Id
@Column(name = "id")
@FilterType(type = Long.class)
private Long id;
(...)
}import io.github.zorin95670.query.SpringQueryFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class ExampleController {
@GetMapping("/myEndpoint")
public Page<MyEntity> find(@RequestParam Map<String, List<String>> allParams,
@PageableDefault(page = 0, size = 10, sort = "lastName", direction = Sort.Direction.ASC) Pageable pageable) {
return myService.find(allParams, queryFilter);
}
}public interface YourEntityRepository extends JpaRepository<YourEntity, Long> {
Page<YourEntity> find(Specification<YourEntity> specification, Pageable pageable);
}Service Interface:
public interface YourEntityService {
(...)
Page<YourEntity> find(Map<String, List<String>> filters, Pageable pageable);
}Service Implementation:
@Service
@Transactional
public class YourEntityServiceImpl implements YourEntityService {
(...)
@Override
public Page<YourEntity> find(final Map<String, List<String>> filters,
final Pageable pageable) {
return this.yourEntityRepository.find(
new QueryFilterSpecification<>(YourEntity.class, filters),
pageable
);
}
}To apply filters without using query parameters directly from the controller, you can manually define filter conditions:
import java.util.HashMap;
import java.util.List;
@Service
@Transactional
public class YourEntityServiceImpl implements YourEntityService {
(...)
@Override
public Page<YourEntity> specificFind() {
Map<String, List<String>> filters = new HashMap<>();
// Filter by id is not equal to 1
filters.put("id", List.of("not_1"));
// Filter by name that begin by "test"
filters.put("name", List.of("lk_test%"));
return this.yourEntityRepository.find(
new QueryFilterSpecification<>(YourEntity.class, filters),
PageRequest.of(0, 10, Sort.by(Sort.Order.asc("name")))
);
}
}If you need support for a custom type, you can extend ComparablePredicateFilter.
Example:
public class YourTypePredicateFilter<T> extends ComparablePredicateFilter<T, YourType> {
public YourTypePredicateFilter(String name, String value) {
super(name, value);
}
@Override
public YourType parseValue(String value) {
// You have to parse the string value to YourType
return YourType.parseYourType(value);
}
}For non-comparable types, extend PredicateFilter directly.
Example:
public abstract class YourTypePredicateFilter<T> extends PredicateFilter<T, YourType> {
YourTypePredicateFilter(String name, String value) {
super(name, value);
}
@Override
public YourType parseValue(String value) {
// You have to parse the string value to YourType
return YourType.parseYourType(value);
}
@Override
public Predicate getPredicate(final int index, final CriteriaBuilder builder, final Expression<YourType> field) {
// Example of content
Predicate predicate;
if (PredicateOperator.INFERIOR.equals(this.getOperator(index))) {
predicate = builder.lessThan(field, parseValue(this.getValue(index)));
} else if (PredicateOperator.SUPERIOR.equals(this.getOperator(index))) {
predicate = builder.greaterThan(field, parseValue(this.getValue(index)));
} else {
predicate = builder.equal(field, parseValue(this.getValue(index)));
}
if (this.getIsNotOperator(index)) {
return builder.not(predicate);
}
return predicate;
}
}Create a class that extends QueryFilterSpecification and override getPredicateFilter:
public class CustomQueryFilterSpecification<T> extends QueryFilterSpecification<T> {
public CustomQueryFilterSpecification(Class<T> entityClass, Map<String, List<String>> filters) {
super(entityClass, filters);
}
@Override
public IPredicateFilter<T, ?> getPredicateFilter(final Class<?> type, final String name, final String value) {
if (Yourtype.class.equals(type)) {
return new YourTypePredicateFilter<>(name, value);
}
// To manage default type
return super.getPredicateFilter(type, name, value);
}
}You can specify a custom field name for the date format by overriding the behavior in your CustomQueryFilterSpecification.
Here's an example implementation:
public class CustomQueryFilterSpecification<T> extends QueryFilterSpecification<T> {
public CustomQueryFilterSpecification(Class<T> entityClass, Map<String, List<String>> filters) {
super(entityClass, filters);
this.setDateFormatFieldName("dateFormat");
}
@Override
public IPredicateFilter<T, ?> getPredicateFilter(final Class<?> type, final String name, final String value) {
if (Yourtype.class.equals(type)) {
return new YourTypePredicateFilter<>(name, value);
}
// To manage default type
return super.getPredicateFilter(type, name, value);
}
}