Spring From the Trenches: Creating a Custom HandlerMethodArgumentResolver

A few weeks ago we learned to transform the value of a single request parameter into an object, which is passed to our controller method as a method parameter, by using Spring type converters.

Using type converters is a good choice if we want to create "simple" value objects and inject these objects into our controller methods.

However, sometimes we want to inject objects that fulfill these requirements:

  • The injected object has more than one field and the values of these fields must be read from different sources.
  • The injected object is not a form object.
  • The injected object is not read from the request body.

If we want to fulfill these requirements, we must create a custom HandlerMethodArgumentResolver, and this blog post describes how we can do it.

Let's start by creating a custom HandlerMethodArgumentResolver that can create FooBar objects.

Creating a Custom HandlerMethodArgumentResolver

The FooBar class is a simple class that has two final fields: bar and foo. Its source code looks as follows:

public class FooBar {

    private final String bar;
    private final String foo;

    FooBar(String bar, String foo) {
        this.bar = bar;
        this.foo = foo;
    }

    public String getBar() {
        return bar;
    }

    public String getFoo() {
        return foo;
    }
}

We can create a custom HandlerMethodArgumentResolver class, which can create FooBar objects, by following these steps:

  1. Implement the HandlerMethodArgumentResolver interface.
  2. Implement the supportsParameter(MethodParameter methodParameter) method. This method must return true if the type of the method parameter is FooBar and false otherwise.
  3. Implement the resolveArgument() method by following these steps:
    1. Get the value of the 'bar' request parameter.
    2. Get the value of the 'foo' request parameter.
    3. If the 'bar' request parameter is not found from the request, use the default value.
    4. If the 'foo' request parameter is not found from the request, use the default value.
    5. Create a new FooBar object and return the created object.

The source code of the FooBarHandlerMethodArgumentResolver class looks as follows:

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public final class FooBarHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(FooBar.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {
        String bar = nativeWebRequest.getParameter("bar");
        String foo = nativeWebRequest.getParameter("foo");

        if (isNotSet(bar)) {
            bar = "defaultBar";
        }

        if (isNotSet(foo)) {
            foo = "defaultFoo";
        }

        return new FooBar(bar, foo);
    }

    private boolean isNotSet(String value) {
        return value == null;
    }
}
The FooBarHandlerMethodArgumentResolver is a simple class that only demonstrates how you can create your own (and probably a lot more complex) method argument resolvers.

Additional Reading:

Before we can use our new HandlerMethodArgumentResolver class, we have to configure the application context of our web application.

Configuring the Application Context of Our Web Application

Before we can inject FooBar objects into our controller methods, we have to register the FooBarHandlerMethodArgumentResolver as a method argument resolver. 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

We can register the FoobarHandlerMethodArgument class as a method argument resolver by making 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 addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) method of the WebMvcConfigurerAdapter class.
  3. Register a custom method argument resolver by creating a new FooBarHandlerMethodArgumentResolver object and adding the creating object to the List given as a method parameter.

The source code of the WebMvcContext class looks as follows:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;
 
@Configuration
@EnableWebMvc
class WebMvcContext extends WebMvcConfigurerAdapter {
 
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new FooBarHandlerMethodArgumentResolver());
    }
}

Let's move on and find out how we can configure the application context of our Spring Boot web application.

Configuring a Spring Boot Web Application

We can register the FoobarHandlerMethodArgument class as a method argument resolver by making the following changes to the "application class" of our Spring Boot web application:

  1. Extend the WebMvcConfigurerAdapter class.
  2. Override the addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) method of the WebMvcConfigurerAdapter class.
  3. Register a custom method argument resolver by creating a new FooBarHandlerMethodArgumentResolver object and adding the creating object to the List given as a method parameter.

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.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class SpringBootExampleApplication extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new FooBarHandlerMethodArgumentResolver());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootExampleApplication.class, args);
    }
}

We are done. Let's find out how we can use our new method argument resolver.

Using the Custom HandlerMethodArgumentResolver

After we have configured the application context of our web application, we can simply add a new FooBar method parameter into our controller method. The source code of our controller class looks as follows:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
final class FooBarController {

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void processFooBar(FooBar fooBar) {
        //Do stuff
    }
}

We can now set the information of the FooBar object by setting the values of the following request parameters:

  • The value of the 'bar' request parameter is set as the value of the bar field.
  • The value of the 'foo' request parameter is set as the value of the foo field.

The following examples demonstrate how we can create new FooBar object, set the values of its bar and foo fields, and inject it into our controller method.

Example 1:

We have to create a FooBar object that fulfills the following requirements:

  • The value of the bar field is 'bar'.
  • The value of the foo field is 'foo'.

We can create this object by sending a GET request to the following url: '/test?bar=bar&foo=foo'.

Example 2:

If we want to create a FooBar and set the default values into its bar and foo fields, we have to send a GET request to the following url: '/test'.

Let's move on and summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • If we want to inject "complex" objects into our controller methods, and these objects aren't form objects or read from the request body, we should create a custom method argument resolver.
  • We can create a new method argument resolver by implementing the HandlerMethodArgumentResolver interface.
  • Before we can use a custom HandlerMethodArgumentResolver, we have to register it as a method argument resolver.

P.S. You can get the example application of this blog post from Github.

16 comments… add one
  • mhewedy Jan 29, 2016 @ 13:36

    I was looking into part of springframewok source code, and I was asking how to register my own Method argument resolver.

    Your post give me the answer in a simple, yet clear and comprehensive way!(since web and boot configs)

    Thanks.

    • Petri Jan 31, 2016 @ 16:10

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

  • Anonymous Feb 10, 2016 @ 13:10

    Hello, it's a very good example but what i want to ask is what if i try to inject (i.e a service class with @autowired anno) a field into my custom argument resolver?

    • Petri Feb 10, 2016 @ 21:56

      You need to follow these steps:

      First, annotate your custom argument resolver class with the @Component annotation and ensure that the Spring container scans the correct package.

      Second, inject the service into the custom argument resolver bean.

      Third, inject your custom argument resolver bean into your configuration class by using field injection:

      
      @Autowired
      private FooBarHandlerMethodArgumentResolver resolver;
      
      

      Fourth, register a custom method argument resolver in your configuration class:

      
      @Override
      public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
              argumentResolvers.add(resolver);
      }
      
      
      • Patrick Mar 1, 2016 @ 22:00

        Hi Petri,

        I'm using a slightly different approach. I extend the HandlerMethodArgumentResolver without adding methods and implement my own interface. Then you are able to autowire a list of custom argument resolvers and add them in a loop. That makes it a lot easier to add additional argument resolvers because you only have to implement the interface and add @Component to your class.

        I'm using this approach also for converters etc.

        Regards,
        Patrick

        • Petri Mar 1, 2016 @ 22:20

          Hi Patrick,

          That sounds interesting. I will try your approach the next time when I have to do this. Thank you for sharing!

  • Raj C Sep 16, 2016 @ 16:17

    Very helpful. Thanks for posting.

    • Petri Sep 19, 2016 @ 18:32

      You are welcome.

  • Johnson eyo Mar 26, 2018 @ 9:03

    Hi petri, thanks for sharing , I have issues using a custom HandlerMethodReturnValueHandler, some how I can't I get it to work for a whole day. Can you write on this ?.
    Cheers

    • Petri Mar 26, 2018 @ 22:18

      Hi,

      Thank you for the suggestion. I have added this blog post on my to-do list, and I will write it after I have finished the next part of my JUnit 5 tutorial.

  • Ivan Kirsanov Oct 14, 2020 @ 9:03

    implement WebMvcConfigurer instead WebMvcConfigurerAdapter to get
    'addResolvers...'

  • David Aug 4, 2022 @ 13:42

    Hi, i have a question. What if i don't very FooBar object sending me defaultBar, defaultFoo.
    What if I want to have one '/test1' whos sending me back defaultBar, defaultFoo, and second something else. What I'm tryin' to say that I want to get those parameters only by calling 'test1'.And how I can do it.
    Best Regards.

Leave a Reply