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.
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
ServletInvocableHandlerMethodand invokes it, which eventually calls
getMethodArgumentValuesto 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
resolveArgumentto 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
WebMvcConfigurerimplementation:
<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
RequestMappingHandlerAdapterregisters 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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.