Spring From the Trenches: Parsing Date and Time Information From a Request Parameter

A few days ago I had to write a controller method that is used to upload files to a Spring web application.

This is an easy task, but I had another requirement that turned out to be a problem: every file has an expiration date that must be given when the file is uploaded.

What went wrong?

The Problem: Failed Type Conversion Attempt

The problem is that Spring cannot convert a request parameter string into an object that contains the date (and time) information. I will demonstrate this problem by using a simple controller that has two methods:

  • The POST requests send to the url: '/api/datetime/date' are handled by the processDate() method.
  • The POST requests send to the url: '/api/datetime/datetime' are handled by the processDateTime() method.

The source code of this controller class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "date", method = RequestMethod.POST)
    public void processDate(@RequestParam("date") LocalDate date) {
		//Do stuff
    }

    @RequestMapping(value = "datetime", method = RequestMethod.POST)
    public void processDateTime(@RequestParam("datetime") LocalDateTime dateAndTime) {
		//Do stuff
    }
}

This controller class has two problems:

  • When we send a POST request to the url: '/api/datetime/date' and set the value of the date request parameter to: 2015-09-26 (ISO 8601 date format), a ConversionFailedException is thrown.
  • When we send a POST request to the url: '/api/datetime/datetime' and set the value of the datetime request parameter to: 2015-09-26T01:30:00.000 (ISO 8601 date and time format), a ConversionFailedException is thrown.

In other words, we cannot use the API provided by the DateTimeController class because it is broken. Let's find out how we can fix it.

@DateTimeFormat Annotation to the Rescue

The Javadoc of the @DateTimeFormat annotation states that it declares that a field should be formatted as a date time. Even though the Javadoc doesn't mention it, we can use this annotation for specifying the pattern that is used by our request parameters.

These examples demonstrate how we can use this annotation:

Example 1:

If we want to use the ISO 8601 date format (yyyy-MM-dd), we have to annotate the controller method parameter with the @DateTimeFormat annotation and set the value of its iso attribute to DateTimeFormat.ISO.DATE. The controller class that uses this date format looks as follows:

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "date", method = RequestMethod.POST)
    public void processDate(@RequestParam("date") 
							@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
        //Do stuff
    }
}

Example 2:

If we want to use a custom date format (like the Finnish date format: dd.MM.yyyy), we have to annotate the controller method parameter with the @DateTimeFormat annotation and set the value of its pattern attribute to 'dd.MM.yyyy'. The controller class that uses this date format looks as follows:

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "date", method = RequestMethod.POST)
    public void processDate(@RequestParam("date") 
							@DateTimeFormat(pattern = "dd.MM.yyyy") LocalDate date) {
        //Do stuff
    }
}
Example 3:

If we want to use the ISO 8601 date and time format (yyyy-MM-dd'T'HH:mm:ss.SSSZ), we have to annotate the controller method parameter with the @DateTimeFormat annotation and set the value of its iso attribute to DateTimeFormat.ISO.DATE_TIME. The controller class that uses this date format looks as follows:

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

	@RequestMapping(value = "datetime", method = RequestMethod.POST)
	public void processDateTime(@RequestParam("datetime") 
								@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
		//Do stuff
    }
}

Example 4:
If we want to use a custom date and time format (like the Finnish date and time format: dd.MM.yyyy HH:mm:ss.SSSZ), we have to annotate the controller method parameter with the @DateTimeFormat annotation and set the value of its pattern attribute to ‘dd.MM.yyyy HH:mm:ss.SSSZ’. The controller class that uses this date format looks as follows:

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

	@RequestMapping(value = "datetime", method = RequestMethod.POST)
	public void processDateTime(@RequestParam("datetime") 
								@DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss.SSSZ") LocalDateTime dateAndTime) {
		//Do stuff
    }
}
We can apply this annotation to java.util.Date, java.util.Calendar, java.long.Long, Joda-Time value types; and as of Spring 4 and JDK 8, to JSR-310 java.time types too.

Let's summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • We can apply the @DateTimeFormat annotation to java.util.Date, java.util.Calendar, java.lang.Long, Joda-Time value types; and as of Spring 4 and JDK 8, to JSR-310 java.time types too.
  • If we want to use the ISO 8601 format, we have to configure the used format by setting the value of the @DateTimeFormat annotation's iso attribute.
  • If we want to use a custom format, we have to configure the used format by setting the value of the @DateTimeFormat annotation's pattern attribute.

P.S. You can get the example application of this blog post from Github.

34 comments… add one
  • Raffaele Litto Sep 28, 2015 @ 10:52

    Is there a global mechanism to regiregister Date and DateTime conversion for parameters? For Spring standard binding , like there is on Jackson object mapper? I'm thinking both on post and get (uri parameters)

  • Ketki Sep 29, 2015 @ 8:21

    Thanks Petri. I remember using String and date formatter for this task but this is a clean way of doing it.

    • Petri Sep 29, 2015 @ 12:41

      You are welcome! However, if you need to process date and time information in multiple controllers, you might want to use type converters -> you don't have to remember to annotate the relevant method parameters with the @DateTimeFormat annotation.

  • Thomas J. Clancy Jan 6, 2016 @ 18:32

    In Spring how would you handle an exception thrown if the date format sent by the client was a bad date? For example I want to return a custom message as JSON stating that the Date string sent is invalid.

  • Rick Aug 19, 2016 @ 5:18

    Petri,
    Thanks for your contribution, as always.
    When using the @DateTimeFormat, as you used it above, I still get parsing errors:

    ConversionFailedException: Failed to convert from type [java.lang.String] to type [@io.swagger.annotations.ApiParam @org.springframework.web.bind.annotation.RequestParam @org.springframework.format.annotation.DateTimeFormat java.time.LocalDate] for value '8/19/2016'; nested exception is java.time.format.DateTimeParseException: Text '8/19/2016' could not be parsed at index 0"

    My controller parameter:
    @RequestParam @DateTimeFormat(pattern = "MM/dd/yyyy") LocalDate startDate

    The input is simply "8/19/2016". Any thoughts.

    • Petri Aug 19, 2016 @ 12:37

      Hi,

      The problem might be that the month has only one digit (e.g. '8' instead of '08'). Have you tried using the pattern: 'M/d/yyyy'?

  • Alex Nov 16, 2016 @ 18:17

    How can you customize the conversion failed error message?

  • Ali Dec 3, 2016 @ 0:07

    I've been stuck on this since Wednesday, THANK YOU SO SO SO SO SO SO SO SO SO MUCH!

    • Petri Dec 10, 2016 @ 22:19

      You are welcome. I am happy to hear that this blog post was useful to you.

  • Innokenty Shuvalov Jan 6, 2017 @ 18:39

    Unfortunately, this is broken in Spring boot 1.3.6 + Spring Cloud Brixton.SR3
    https://github.com/spring-cloud/spring-cloud-netflix/issues/1178

    • Petri Jan 12, 2017 @ 21:42

      It seems so :(

  • Jan Larsen Jan 12, 2017 @ 13:58

    Thanks. This hit the spot.

    • Petri Jan 12, 2017 @ 21:43

      You are welcome! I am happy to hear that this blog post was useful to you.

  • Srini Aug 29, 2017 @ 2:01

    Hi Petri,
    What if my json request has one property which is date like '2017-08-29' and i want customer with registration date to be set to entity ( which is string ); how it should be handled?

    • Petri Sep 14, 2017 @ 22:09

      Hi,

      You can simply add a LocalDate field to your DTO and the Jackson ObjectMapper will transform the String object into a LocalDate object (and vice versa).

  • Gordy Bone Sep 13, 2017 @ 15:54

    Just what I needed. Thanks!

    • Petri Sep 14, 2017 @ 22:02

      You are welcome!

  • Sushant Oct 28, 2017 @ 8:52

    How to handle null date?

    • Petri Oct 28, 2017 @ 10:51

      Hi,

      The @RequestParam annotation has an attribute called required. This attribute is used to define whether the request is required or not. Because the default value of this attribute is true, you have to change the value of this attribute to false.

      This allows you to handle the situations when the request parameter is not given or when its value is an empty string (in both cases, the method parameter of your controller method will be null).

  • Raymond Oct 30, 2017 @ 19:06

    Hi Petri,

    How do you use this if the LocalDate field is inside a request DTO? I tried putting @DateTimeFormat in the DTO's constructor, on the field, and even in the controller method parameter, but none have worked.

    • Petri Oct 30, 2017 @ 22:04

      Hi Raymond,

      You can either change the configuration of your ObjectMapper or you can use the @JsonFormat annotation. Take a look at this blog post. If you are using Spring Boot, you should take a look at its reference manual.

      • Raymond Nov 2, 2017 @ 17:57

        Hi Petri,

        Thank you for the quick response. Turns out the module wasn't registered with the ObjectMapper all this time.

        Cheers!

        • Petri Nov 5, 2017 @ 21:53

          You are welcome! Also, it's good to hear that you were able to solve your problem.

  • Prathyusha Feb 22, 2018 @ 19:44

    My controller is @DateTimeFormat( iso = DateTimeFormat.ISO.Date) LocalDate startDate when send request as startDate as 2017-08-08T00:00:00.000Z or as 2017/08/08 I'm getting failed to convert from type string to type LocalDate

    • Petri Feb 22, 2018 @ 21:38

      Hi,

      If you use the DateTimeFormat.ISO.Date, your request parameter must use the format: yyyy-MM-dd.

  • Kupci Jun 12, 2018 @ 0:46

    Appreciate the link, I've always set the param as a string and then converted it myself, this seems a better way to enforce the contract within the method signature. And if you do want to keep the Zone, here's how you would do that: https://stackoverflow.com/questions/34398387/zoneddatetime-as-pathvariable-in-spring-rest-requestmapping
    paraphrasing here, but the idea is to use a custom converter.
    @PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime startDate

  • iam Jul 16, 2019 @ 9:07

    how does use custom error messages in Spring boot with thymeleaf? I'm trying but just appears the error page so I get stuck now for 3 days lol

    • Petri Jul 16, 2019 @ 19:59

      Do you want to display one custom error page or use different error pages for different errors?

  • Gowtham Oct 4, 2019 @ 13:00

    Is it possible to provide default value as current date ?

  • Adair Lima. Apr 1, 2021 @ 23:44

    Thank you very much. this post same my day. Keep the good work. From Cabo Verde Islands

Leave a Reply