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.

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

During getMethodArgumentValues, Spring iterates over registered resolvers, checks supportsParameter, and invokes resolveArgument to obtain the value.

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

Custom Parameter Resolver

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

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomParam {}
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();
  }
}

Register the resolver in a WebMvcConfigurer implementation:

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

Usage example:

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

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.

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

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.