Learn to write tests which are easy to read and write: get started with Spock Framework.

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?

My "Test With Spring" course helps you to write unit, integration, and end-to-end tests for Spring and Spring Boot Web Apps:

CHECK IT OUT >>

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
    }
}

My "Test With Spring" course helps you to write unit, integration, and end-to-end tests for Spring and Spring Boot Web Apps:

CHECK IT OUT >>


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.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

26 comments… add one
  • 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)

    Reply
    • Yes. You can use a type converter. I will write a new blog post about this, but you can get started by reading this StackOverflow question.

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

    Reply
    • 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.

      Reply
  • 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.

    Reply
  • 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.

    Reply
    • 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’?

      Reply
  • How can you customize the conversion failed error message?

    Reply
  • I’ve been stuck on this since Wednesday, THANK YOU SO SO SO SO SO SO SO SO SO MUCH!

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

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

    Reply
    • It seems so :(

      Reply
  • Thanks. This hit the spot.

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

      Reply
  • 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?

    Reply
    • 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).

      Reply
  • Just what I needed. Thanks!

    Reply
    • You are welcome!

      Reply
  • How to handle null date?

    Reply
    • 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).

      Reply
  • 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.

    Reply
    • 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.

      Reply
      • Hi Petri,

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

        Cheers!

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

          Reply

Leave a Comment