Backend Development 7 min read

How Spring MVC Resolves Controller Parameters and Builds Custom Argument Resolvers

This article explains the internal workflow of Spring MVC’s HandlerMethodArgumentResolver for processing controller method parameters, walks through the core source code steps, and demonstrates how to implement and register a custom argument resolver using a bespoke annotation.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring MVC Resolves Controller Parameters and Builds Custom Argument Resolvers

Overview

Any controller method in Spring Boot 2.4.11 can accept parameters of various types, and Spring handles the conversion automatically. The core component responsible for this is

HandlerMethodArgumentResolver

, which determines if it can handle a parameter and performs the actual resolution.

Parameter Resolver Working Principle

When a request arrives, Spring follows these steps:

Servlet entry

HandlerMapping

HandlerAdapter

HandlerMethodArgumentResolver – resolves method arguments

HandlerMethodReturnValueHandler

The focus is on step 4. Spring creates a

ServletInvocableHandlerMethod

and invokes it, which eventually calls

getMethodArgumentValues

to obtain argument values.

<code>public interface HandlerMethodArgumentResolver {
  boolean supportsParameter(MethodParameter parameter);
  Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}</code>

During

getMethodArgumentValues

, Spring iterates over registered resolvers, checks

supportsParameter

, and invokes

resolveArgument

to obtain the value.

<code>protected Object[] getMethodArgumentValues(NativeWebRequest request,
                                            @Nullable ModelAndViewContainer mavContainer,
                                            Object... providedArgs) throws Exception {
  // Find a suitable resolver
  if (!this.resolvers.supportsParameter(parameter)) {
    throw new IllegalStateException("No suitable resolver");
  }
  // Resolve the argument
  args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}</code>

Custom Parameter Resolver

To handle parameters annotated with a custom annotation, define the annotation and resolver:

<code>@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomParam {}
</code>
<code>public class CustomHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterAnnotation(CustomParam.class) != null;
  }

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    String name = getParameterName(parameter);
    String value = webRequest.getParameter(name);
    DataBinder binder = binderFactory.createBinder(webRequest, null, name);
    ConversionService cs = binder.getConversionService();
    if (cs != null) {
      TypeDescriptor source = TypeDescriptor.valueOf(String.class);
      TypeDescriptor target = new TypeDescriptor(parameter);
      if (cs.canConvert(source, target)) {
        return binder.convertIfNecessary(value, parameter.getParameterType(), parameter);
      }
    }
    return null;
  }

  private String getParameterName(MethodParameter parameter) {
    Class<?> clazz = parameter.getParameterType();
    return clazz.getSimpleName().toLowerCase();
  }
}
</code>

Register the resolver in a

WebMvcConfigurer

implementation:

<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new CustomHandlerMethodArgumentResolver());
  }
}
</code>

Usage example:

<code>@GetMapping("/uk")
public Users uk(@CustomParam Users user) {
  return user;
}
</code>

System Built‑in Resolvers

Spring’s

RequestMappingHandlerAdapter

registers a default list of resolvers, covering annotation‑based, type‑based, and catch‑all strategies, and also incorporates any custom resolvers added by the developer.

<code>private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
  List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);
  resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
  // ... other built‑in resolvers
  resolvers.add(new ServletRequestMethodArgumentResolver());
  resolvers.add(new ServletResponseMethodArgumentResolver());
  if (getCustomArgumentResolvers() != null) {
    resolvers.addAll(getCustomArgumentResolvers());
  }
  resolvers.add(new PrincipalMethodArgumentResolver());
  resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
  return resolvers;
}
</code>

Understanding this flow enables developers to customize how Spring binds request data to controller method parameters.

JavaSpring BootSpring MVCHandlerMethodArgumentResolverCustom Argument Resolver
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.