Spring From the Trenches: Using Type Converters With Spring MVC

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:

  1. Create a LocalDateConverter class.
  2. Implement the Converter<S, T> interface, and set the source type to String and the target type to LocalDate.
  3. Add a private DateTimeFormatter field to the created class.
  4. 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.
  5. 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);
    }
}
If you are using Spring Framework 4.1 (or newer), you don't have to ensure that the source string is not null because Spring Framework guarantees that it is never null. However, if you use Spring 4.0 (or older), you might want to check that the source string is not null because the Javadoc of the Converter<S, T> interface doesn't state that the method parameter of the convert() method cannot be null.

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:

  1. Create a LocalDateTimeConverter class.
  2. Implement the Converter<S, T> interface, and set the source type to String and the target type to LocalDateTime.
  3. Add a private DateTimeFormatter field to the created class.
  4. 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.
  5. 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);
    }
}
If you are using Spring Framework 4.1 (or newer), you don't have to ensure that the source string is not null because Spring Framework guarantees that it is never null. However, if you use Spring 4.0 (or older), you might want to check that the source string is not null because the Javadoc of the Converter<S, T> interface doesn't state that the method parameter of the convert() method cannot be null.

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:

  1. Extend the WebMvcConfigurerAdapter class.
  2. Override the addFormatters(FormatterRegistry registry) method of the WebMvcConfigurerAdapter class.
  3. 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:

  1. Extend the WebMvcConfigurerAdapter class.
  2. Override the addFormatters(FormatterRegistry registry) method of the WebMvcConfigurerAdapter class.
  3. 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:

  1. Add a new method parameter into our controller method and set the type of this method parameter to LocalDate or LocalDateTime.
  2. 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.

27 comments… add one
  • Harihar das Oct 12, 2015 @ 22:08

    Very helpful. Just the kind of technique I was looking for, for a Spring boot application. I am gonna use it right away.

    • Petri Oct 12, 2015 @ 22:40

      Great! I am happy hear that this blog post was useful to you.

  • Rob Oct 13, 2015 @ 13:46

    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?

    
    @ResponseBody
    @RequestMapping("/test")
    public String test(Integer num)
    {
      return "test" + num;
    }
    
    
    
    GET /test
    > test null
    
    GET /test?num=100
    > test 100
    
    
    • Petri Oct 13, 2015 @ 21:03

      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.

  • Otto Oct 13, 2015 @ 17:44

    Anyone interested in that picture it's from Hill 60 in Ypres, Belgium. (World War 1)
    Visited that site a few year ago.

    • Petri Oct 13, 2015 @ 20:57

      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!

  • Darko Oct 15, 2015 @ 11:33

    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.

    • Petri Oct 15, 2015 @ 19:08

      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.

      • Darko Oct 15, 2015 @ 20:05

        Thank you for the response. Will give it a shot!

        • Petri Oct 16, 2015 @ 19:57

          You are welcome! Let me know if you could solve your problem. If not, I can write another blog post that covers it.

  • Abdelilah Oct 21, 2015 @ 12:05

    Very useful,
    For Spring roo user, to add conversion service create a class that implements FormattingConversionServiceFactoryBean and annotated @RooConversionService

    • Petri Oct 21, 2015 @ 20:16

      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.

  • Patrick Jan 27, 2016 @ 10:26

    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

    • Petri Jan 29, 2016 @ 20:02

      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.

      • Patrick Feb 3, 2016 @ 18:34

        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

  • zhuguowei Apr 1, 2016 @ 17:31

    Thanks! very clear!

  • Ravi Jul 28, 2017 @ 17:20

    Suppose the addFormmatters in xml configuration what we need to do?

    • Ravi Jul 28, 2017 @ 17:22

      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.

  • Christian Aug 8, 2017 @ 14:24

    Spring guarantees that the argument passed to the convert method is not null, so there is no need to handle this case.

    • Petri Aug 9, 2017 @ 17:14

      Good catch. It seems that the Javadoc of the Converter interface was updated when Spring Framework 4.1 was released:

      • Spring 4.0 (doesn't say that the source string cannot be null)
      • Spring 4.1 (states that the source string cannot be null)

      I will update the blog post. Thank you for pointing this out!

  • Gordy Bone Sep 8, 2017 @ 12:10

    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.

    • Petri Sep 10, 2017 @ 9:04

      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.

  • George Sep 14, 2017 @ 14:23

    Anyway to use a map inside a converter for example: Converter<Map, Object>?

Leave a Reply