Overview

Parsing of LocalDate query parameters in Spring Boot

No Comments

Spring Boot is a framework that follows the convention over configuration principle. Whenever a convention does not do the right thing for you, it is usually easily possible to configure the desired behavior. As I have discovered lately, there is currently no easy way to configure the parsing behavior for LocalDate instances used as query parameters. In this blog post I will present my findings as well as the idiomatic solution. The complete code shown in this blog post is available at GitHub.

LocalDate as query parameter

Spring MVC provides a simple API for defining web endpoints. Say I want to have an end point which returns all orders for a date given as a query parameter, so I can query it like this:

GET /orders?date=2017-08-04

I could implement this using Spring Boot and Spring MVC like this:

@RestController
@RequestMapping("/orders")
public class OrderController {
 
  @GetMapping
  public ResponseEntity<List<Order>> getOrdersByDate(
       @RequestParam(name = "date")
       @DateTimeFormat(iso = ISO.DATE)
       LocalDate date) {
    // retrieve and return orders by date
  }
}

Note that I have to specify a @DateTimePattern and set it to ISO.DATE. This enables parsing of query parameters with pattern yyyy-MM-dd. Now lets say I want the query parameter to be optional (and return all the orders, if no date query parameter is given). This can be configured by setting the required option of the @RequestParam annotation to false. Spring will pass null to my handler method if no query parameter is given. Since I don’t like to work with null in my code, I’d rather use an Optional as parameter type:

@GetMapping
public ResponseEntity<List<Order>> getOrdersByDate(
     @RequestParam(name = "date", required = false)
     @DateTimeFormat(iso = ISO.DATE)
     Optional<LocalDate> date) {
  // retrieve and return orders by date
}

Configuring the pattern globally – first approach

The examples above work well for relatively small applications. But for bigger applications I want to configure the pattern for my date query parameters in one central place. Unfortunately there is currently no configuration property in Spring Boot that can be used for this. The spring.mvc.date-pattern property only applies to parameters of type java.util.Date. So after googling a while I found this StackOverflow answer, which boils down to implementing a @ControllerAdvice:

@ControllerAdvice
public class LocalDateControllerAdvice {
 
  @InitBinder
  public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
      @Override
      public void setAsText(String text) throws IllegalArgumentException {
        LocalDate.parse(text, DateTimeFormatter.ISO_DATE);
      }
    });
  }
}

The controller advice gets applied to all controller instances. It uses the WebDataBinder to register a custom editor for the LocalDate class. Inside the editor implementation the predefined DateTimeFormatter.ISO_DATE is used to parse strings into LocalDate. While this is not as convenient as a configuration property, it looks good enough for me. And if we try it out, if works for GET requests without date query parameter. But what happens if I try to query the orders for a specific date again? Let’s see:

GET /orders?date=2017-07-12

HTTP/1.1 400
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Fri, 04 Aug 2017 13:32:54 GMT
Transfer-Encoding: chunked

{
    "error": "Bad Request",
    "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
    "message": "Failed to convert value of type 'java.lang.String' to required type 'java.util.Optional'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.time.LocalDate] for value '2017-07-12'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2017-07-12]",
    "path": "/orders",
    "status": 400,
    "timestamp": "2017-08-04T13:32:54Z"
}

Unfortunately this solution does not work with the Optional parameter.

Idiomatic solution possible changes in future Spring Boot releases

After this setback I decided to have a look at how the spring.mvc.date-pattern property is implemented for java.util.Date. The implementation can be found in WebMvcAutoConfiguration:

@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
public Formatter<Date> dateFormatter() {
  return new DateFormatter(this.mvcProperties.getDateFormat());
}

So it looks like in Spring Boot, instead of registering a custom editor for LocalDate I have to register an implementation of Formatter. So let’s give that a try:

@Bean
public Formatter<LocalDate> localDateFormatter() {
  return new Formatter<LocalDate>() {
    @Override
    public LocalDate parse(String text, Locale locale) throws ParseException {
      return LocalDate.parse(text, DateTimeFormatter.ISO_DATE);
    }
 
    @Override
    public String print(LocalDate object, Locale locale) {
      return DateTimeFormatter.ISO_DATE.format(object);
    }
  };
}

This solution does work with optional as well as with non optional parameters. Since it looks like this is missing in Spring Boot, I’ve already created a pull request to add this to Spring Boot. So maybe this will become available as a configuration property in future release.

Conclusion

It is currently not possible to configure the behavior parsing of LocalDate used as query parameters via configuration properties in Spring Boot. One possible solution is to register a custom editor using a @ControllerAdvice. While this solution works for parameters of type LocalDate, it does not work with parameters of type Optional. The idiomatic solution that works with both types, is to implement a Formatter. This solution may become part of Spring Boot itself in future releases.

Benedikt Ritter works as a Software Craftsman at codecentric AG in Solingen since September 2013. His joy for creating reliable software is not limited to coding at work: Benedikt is member of the Apache Software Foundation and Committer for the Apache Commons project.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Comment

Your email address will not be published. Required fields are marked *