A few weeks ago we learned how we can parse date and time information from a request parameter by using the @DateTimeFormat annotation.
This is a simple and clean solution that works very well if we don't have "too many" controller methods that use this approach.
The drawback of this solution is that we have to annotate the relevant method parameters with the @DateTimeFormat annotation and configure the used date and time format every time when we want to parse date and time information from a request parameter.
This blog post describes how we can solve that problem by using Spring type converters.
Let's start by creating the type converters that can convert our request parameters into Java 8 LocalDate and LocalDateTime objects.
Creating the Type Converters
We can create a type converter by implementing the Converter<S, T> interface. When we implement that interface, we have to provide two type parameters that are described in the following:
- The S type parameter describes type of the source object.
- The T type parameter describes the type of the target object.
Because we want to support both Java 8 LocalDate and LocalDateTime objects, we have create two type converters. Let's start by creating a type converter that converts String objects into Java 8 LocalDate objects.
Converting String Objects Into Java 8 LocalDate Objects
We can create a type converter that converts String objects into Java 8 LocalDate objects by following these steps:
- Create a LocalDateConverter class.
- Implement the Converter<S, T> interface, and set the source type to String and the target type to LocalDate.
- Add a private DateTimeFormatter field to the created class.
- Add a constructor, which takes the used date format as a method parameter, to the created class and implement it by creating a new DateTimeFormatter object.
- Override the convert() method by following these rules:
- If the source string is null or empty, return null.
- If the source string is not null or empty, return a new LocalDate object.
The source code of the LocalDateConverter class looks as follows:
import org.springframework.core.convert.converter.Converter; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public final class LocalDateConverter implements Converter<String, LocalDate> { private final DateTimeFormatter formatter; public LocalDateConverter(String dateFormat) { this.formatter = DateTimeFormatter.ofPattern(dateFormat); } @Override public LocalDate convert(String source) { if (source == null || source.isEmpty()) { return null; } return LocalDate.parse(source, formatter); } }
Additional Reading:
Let's find out how we can create a type converter that converts String objects into Java 8 LocalDateTime objects.
Converting String Objects Into Java 8 LocalDateTime Objects
We can create a type converter that converts String objects into Java 8 LocalDateTime objects by following these steps:
- Create a LocalDateTimeConverter class.
- Implement the Converter<S, T> interface, and set the source type to String and the target type to LocalDateTime.
- Add a private DateTimeFormatter field to the created class.
- Add a constructor, which takes the used date and time format as a method parameter, to the created class and implement it by creating a new DateTimeFormatter object.
- Override the convert() method by following these rules:
- If the source string is null or empty, return null.
- If the source string is not null or empty, return a new LocalDateTime object.
The source code of the LocalDateTimeConverter class looks as follows:
import org.springframework.core.convert.converter.Converter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public final class LocalDateTimeConverter implements Converter<String, LocalDateTime> { private final DateTimeFormatter formatter; public LocalDateTimeConverter(String dateFormat) { this.formatter = DateTimeFormatter.ofPattern(dateFormat); } @Override public LocalDateTime convert(String source) { if (source == null || source.isEmpty()) { return null; } return LocalDateTime.parse(source, formatter); } }
Additional Reading:
Before we can use our new type converters, we have to configure the application context of our web application.
Configuring the Application Context of Our Web Application
Before we can use our type converters, we have to add them to the Spring formatter registry. This section describes how we can configure the application context of our web application that uses either Spring or Spring Boot.
Configuring a Spring Web Application
If we want to add our type converters into the formatter registry of a Spring web application, we have to make the following changes to the configuration class that configures the web layer of our Spring web application:
- Extend the WebMvcConfigurerAdapter class.
- Override the addFormatters(FormatterRegistry registry) method of the WebMvcConfigurerAdapter class.
- Implement this method by adding our type converters into the Spring formatter registry.
The source code of the WebMvcContext class looks as follows:
import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration @EnableWebMvc class WebMvcContext extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new LocalDateConverter("yyyy-MM-dd")); registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS")); } }
Let's move on and find out how we can add our type converters into the formatter registry of a Spring Boot web application.
Configuring a Spring Boot Web Application
If we want to add our type converters into the formatter registry of a Spring Boot web application, we have to make the following changes to the "application class" of our Spring Boot web application:
- Extend the WebMvcConfigurerAdapter class.
- Override the addFormatters(FormatterRegistry registry) method of the WebMvcConfigurerAdapter class.
- Implement this method by adding our type converters into the Spring formatter registry.
The source code of the SpringBootExampleApplication class looks as follows:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration @EnableAutoConfiguration @ComponentScan public class SpringBootExampleApplication extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new LocalDateConverter("yyyy-MM-dd")); registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS")); } public static void main(String[] args) { SpringApplication.run(SpringBootExampleApplication.class, args); } }
We are done. Let's find out how we can use our type converters.
Using the Type Converters
After we have configured the application context of our web application, we can use our type converters by following these steps:
- Add a new method parameter into our controller method and set the type of this method parameter to LocalDate or LocalDateTime.
- Configure the name of the request parameter by annotating the method parameter with the @RequestParam annotation.
The source code of our 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 } }
Let's move on and summarize what we learned from this blog post.
Summary
This blog post has taught us two things:
- We can create a new type converter by implementing the Converter<S, T> interface.
- Before we can use our type converter, we have to add it into the Spring formatter registery. We can do this by extending the WebMvcConfigurerAdapter class and overriding its addFormatters(FormatterRegistry registry) method.
P.S. You can get the example application of this blog post from Github.
Very helpful. Just the kind of technique I was looking for, for a Spring boot application. I am gonna use it right away.
Great! I am happy hear that this blog post was useful to you.
Hi Petri, thanks for another great post.
I have a question related to Spring MVC and couldn't find a definitive answer in the docs, under what circumstances is @RequestParam required for controller parameters? It seems that if it's missing then Spring MVC will do its best to try and resolve it from the request, is there any documentation around the order or mechanism for how parameter resolution works?
Hi Rob,
Unfortunately I don't know the answer to your question either. That is why I always use the
@RequestParam
annotation. I posted your question to Twitter. Let's hope that someone can answer to your question.Anyone interested in that picture it's from Hill 60 in Ypres, Belgium. (World War 1)
Visited that site a few year ago.
Thank you for telling this to me.
I bought this photo from Depositphotos. They say that it is from WW 1, but they didn't disclose the exact location. Because you told the exact location of that bunker, I found a Wikipedia article that talks about the battle of Hill 60. Now I have to read it ;)
Thanks again!
Will this also work for nested objects in @RequestBody requests?
I've been looking for a way to implement a default deserialization behavior for dates and custom classes but to no avail.
No, but you can use Jackson modules for this purpose. For example, if you use Java 8, you should take a look at the JDK 8 datatypes for Jackson. On the other hand, if you use Joda-Time, you can use Joda datatypes for Jackson.
If you want to implement a custom serializer, take a look at this blog post: Jackson – Custom Serializer.
Thank you for the response. Will give it a shot!
You are welcome! Let me know if you could solve your problem. If not, I can write another blog post that covers it.
Very useful,
For Spring roo user, to add conversion service create a class that implements FormattingConversionServiceFactoryBean and annotated @RooConversionService
Thank you for your kind words. I really appreciate them. Also, thank you for sharing that Spring Roo tip. It is useful to everyone who uses Spring Roo.
Hi Petri,
thanks for this post. Do you also know how to keep Springs default Date converter but add support for one or two additional date formats globally?
Regards,
Patrick
Hi Patrick,
Unfortunately I don't know the answer to your question. You could create a custom type converter that identifies the used date format and creates the Date objects by using this date format. However, I am pretty sure that there is a better way to solve this problem. If you find one, let me know.
Hi Petri,
what you describe is exactly the solution we currently use. In my opinion Spring is missing a specialized Converter interface that declares a "supports" method. When you implement a new application you probably wouldn't have this problem, but actually we have to support dd.MM.yyyy dates for backward compatibility and additionally we now support dates in ISO8601 format.
Regards,
Patrick
Thanks! very clear!
Suppose the addFormmatters in xml configuration what we need to do?
public class SpringBootExampleApplication extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LocalDateConverter("yyyy-MM-dd"));
registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS"));
}
I want the above to convert in xml configuration .Post the xml configuration for the same.
Hi,
Take a look at the Spring Reference Manual: 8.5.5 Configuring a ConversionService. It explains how you can register custom converters by using XML configuration.
Spring guarantees that the argument passed to the convert method is not null, so there is no need to handle this case.
Good catch. It seems that the Javadoc of the
Converter
interface was updated when Spring Framework 4.1 was released:I will update the blog post. Thank you for pointing this out!
Great post. Thank you for this.
Just to add that in a Spring Boot application you just need to annotate your converter with @Component: component scan will then detect it automatically.
Hi,
Good catch. I will update the blog post because I assume that some people might miss this information just because they don't want to read the comments.
Thank you for letting me know.
Anyway to use a map inside a converter for example: Converter<Map, Object>?