Intro
Let's say in a Spring boot application there is a person repository interface that extends JpaRepository
, QuerydslPredicateExecutor
and QuerydslBinderCustomizer
that looks like this:
@RepositoryRestResource(path = "persons")
public interface PersonRepository
extends JpaRepository<Person, Long>, QuerydslPredicateExecutor<Person>,
QuerydslBinderCustomizer<QPerson> {
@Override
default void customize(QuerydslBindings bindings, QPerson root) {
bindings.bind(String.class)
.first((StringPath path, String value) -> (path.eq(value)));
bindings.bind(LocalDate.class)
.first((DatePath<LocalDate> path, LocalDate value) -> (path.eq(value)));
}
}
The Person
class (a JPA entity) that belongs to this repository simply holds the attributes name: String
and dateOfBirth: LocalDate
.
Given this, there are collection endpoints to query the (magic provided) controller that belongs to the repository like this (here to query all persons whose name equals something):
curl "http://localhost:8080/persons?name=John"
This request is processed successfully.
Question
But how to (centralized) define the date format for querying all persons, whose birthday is equals something?
I want to query the repository like this (in the format yyyy-MM-dd):
curl "http://localhost:8080/persons?dateOfBirth=1987-12-15"
The system locale the application is running on is set to en_US, so the date format is like MM/dd/yy. (Using this date format for querying, the request is also processed successfully).
First (unsuccessfull) thought/attempt
To use the intended format, my first thought was to define the date format in the application properties for spring boot:
spring.mvc.format.date: yyyy-MM-dd
But this approach leads to an DateTimeParseException
saying that the date in format yyyy-MM-dd
could not be parsed:
...
Caused by: java.lang.IllegalArgumentException: Parse attempt failed for value [2020-01-01]
at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:223) ~[spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.2.10.RELEASE.jar:5.2.10.RELEASE]
... 56 common frames omitted
Caused by: java.time.format.DateTimeParseException: Text '2020-01-01' could not be parsed at index 4
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046) ~[na:na]
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948) ~[na:na]
at java.base/java.time.LocalDate.parse(LocalDate.java:428) ~[na:na]
at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:69) ~[spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:46) ~[spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:217) ~[spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
("Regular" repository resources created manually which takes )LocalDate
as parameter use the desired date format, so requests like .../persons/search/findByDateOfBirth=1987-12-15
are processed successfully
For a full example, I created an executable sample project including some tests.
Providing a custom Converter
with a RepositoryRestConfigurer
solved my problem, so it was possible to specify the date format (Jsr310Converters.StringToLocalDateConverter.INSTANCE
uses DateTimeFormatter.ISO_DATE
):
@Component
public class LocalDateConfiguration implements RepositoryRestConfigurer {
@Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(StringToLocalDateConverter.INSTANCE);
}
}
In general:
@Component
public class LocalDateConfiguration implements RepositoryRestConfigurer {
@Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
// Use any format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(source, formatter);
}
});
}
}