Search code examples
springspring-bootspring-data-restquerydsljava-time

How to define request date format for Spring Data Rest repositories and Querydsl


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.


Solution

  • 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);                                      
          }                                                                                   
        });                                                                                     
      }                                                                                           
                                                                                                    
    }