I released the intermediate package of my Test With Spring course. Take a look at the course >>

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.

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

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

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

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.

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

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.

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 →

19 comments… add one
  • Very helpful. Just the kind of technique I was looking for, for a Spring boot application. I am gonna use it right away.

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

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

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

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

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

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

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

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

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

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

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

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

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

        Reply
  • Thanks! very clear!

    Reply

Leave a Comment