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:
1import org.springframework.beans.factory.annotation.Autowired;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RequestMethod;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7import java.time.LocalDate;
8import java.time.LocalDateTime;
9
10@RestController
11@RequestMapping("/api/datetime/")
12final class DateTimeController {
13
14 @RequestMapping(value = "date", method = RequestMethod.POST)
15 public void processDate(@RequestParam("date") LocalDate date) {
16 //Do stuff
17 }
18
19 @RequestMapping(value = "datetime", method = RequestMethod.POST)
20 public void processDateTime(@RequestParam("datetime") LocalDateTime dateAndTime) {
21 //Do stuff
22 }
23}
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:
1import org.springframework.format.annotation.DateTimeFormat;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RequestMethod;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7import java.time.LocalDate;
8
9@RestController
10@RequestMapping("/api/datetime/")
11final class DateTimeController {
12
13 @RequestMapping(value = "date", method = RequestMethod.POST)
14 public void processDate(@RequestParam("date")
15 @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
16 //Do stuff
17 }
18}
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:
1import org.springframework.format.annotation.DateTimeFormat;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RequestMethod;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7import java.time.LocalDate;
8
9@RestController
10@RequestMapping("/api/datetime/")
11final class DateTimeController {
12
13 @RequestMapping(value = "date", method = RequestMethod.POST)
14 public void processDate(@RequestParam("date")
15 @DateTimeFormat(pattern = "dd.MM.yyyy") LocalDate date) {
16 //Do stuff
17 }
18}
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:
1import org.springframework.format.annotation.DateTimeFormat;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RequestMethod;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7import java.time.LocalDateTime;
8
9@RestController
10@RequestMapping("/api/datetime/")
11final class DateTimeController {
12
13 @RequestMapping(value = "datetime", method = RequestMethod.POST)
14 public void processDateTime(@RequestParam("datetime")
15 @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
16 //Do stuff
17 }
18}
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:
1import org.springframework.format.annotation.DateTimeFormat;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.RequestMethod;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7import java.time.LocalDateTime;
8
9@RestController
10@RequestMapping("/api/datetime/")
11final class DateTimeController {
12
13 @RequestMapping(value = "datetime", method = RequestMethod.POST)
14 public void processDateTime(@RequestParam("datetime")
15 @DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss.SSSZ") LocalDateTime dateAndTime) {
16 //Do stuff
17 }
18}
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.