skip to content

Walk through what RequestMappingHandlerAdapter.invokeHandlerMethod does when a @RequestMapping method is called.

level: middleimportance: must knowfreq 45%

answer

  1. HandlerMethod → ServletInvocableHandlerMethod
  2. inject arg-resolver + return-handler composites
  3. ModelAndViewContainer threads through
  4. invokeAndHandle: resolve args, reflect-invoke, handle return
  5. async started → return null, re-dispatch later

basics

~20 s

It wraps the controller method in a ServletInvocableHandlerMethod, plugs in the argument resolvers and return-value handlers, binds a ModelAndViewContainer, resolves each parameter, invokes the method, and hands the return value to the right return-value handler.

solid answer

~30 s

When RequestMappingHandlerAdapter.handleInternal runs, it calls invokeHandlerMethod. There it builds a ServletInvocableHandlerMethod from the HandlerMethod and injects the shared HandlerMethodArgumentResolverComposite and HandlerMethodReturnValueHandlerComposite, plus a WebDataBinderFactory and a ModelFactory. It creates a ModelAndViewContainer, initializes model attributes (@ModelAttribute methods, @SessionAttributes), then calls invokeAndHandle. invokeAndHandle resolves every method argument by asking each argument resolver supportsParameter/resolveArgument, reflectively invokes the controller method, and passes the return value to the first return-value handler whose supportsReturnType is true (e.g. RequestResponseBodyMethodProcessor for @ResponseBody). Finally getModelAndView turns the ModelAndViewContainer into a ModelAndView (or null if the request was marked handled). Async returns (Callable, DeferredResult) are detected and delegated to WebAsyncManager.

code

java · 25 lines
java
// Paraphrase of RequestMappingHandlerAdapter.invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response,
                                           HandlerMethod handlerMethod) throws Exception {
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocable = createInvocableHandlerMethod(handlerMethod);
    invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    invocable.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    invocable.setDataBinderFactory(binderFactory);
    invocable.setParameterNameDiscoverer(this.parameterNameDiscoverer);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    modelFactory.initModel(webRequest, mavContainer, invocable);

    // ...async (WebAsyncManager) wiring omitted...

    invocable.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
        return null; // will be re-dispatched
    }
    return getModelAndView(mavContainer, modelFactory, webRequest);
}

go deeper

for a junior

Know invokeHandlerMethod is where the controller method actually gets called, with parameters filled in automatically.

for a middle

Trace the wrapping into ServletInvocableHandlerMethod, injection of the composites, and invokeAndHandle resolving args then handling the return.

for a senior

Discuss ModelAndViewContainer threading, requestHandled semantics, and how the null ModelAndView flows back to DispatcherServlet.

for a principal

Cover async short-circuit via WebAsyncManager, provided-args injection, and the performance rationale of shared stateless composites.

## Where this sits `RequestMappingHandlerAdapter` is the `HandlerAdapter` for annotated controller methods. Its `handle(...)` → `handleInternal(...)` → **`invokeHandlerMethod(request, response, handlerMethod)`** is where a single `@RequestMapping` method actually gets executed. Everything about how parameters get filled and how return values become responses is wired here. ## Key collaborators - **`HandlerMethod`** — an immutable descriptor of the controller bean + `java.lang.reflect.Method` (produced by `HandlerMapping`). - **`ServletInvocableHandlerMethod`** — an *invocable* extension of `HandlerMethod` that can (a) resolve arguments, (b) call the method, and (c) handle the return value. This is the workhorse object. - **`HandlerMethodArgumentResolverComposite`** — the ordered list of `HandlerMethodArgumentResolver`s (for `@RequestParam`, `@PathVariable`, `@RequestBody`, `Model`, `Pageable`, etc.). - **`HandlerMethodReturnValueHandlerComposite`** — the ordered list of `HandlerMethodReturnValueHandler`s (for `@ResponseBody`, `ModelAndView`, `String` view names, `ResponseEntity`, etc.). - **`WebDataBinderFactory`** / **`ModelFactory`** — create `WebDataBinder`s (validation/conversion) and populate the model from `@ModelAttribute` methods and `@SessionAttributes`. - **`ModelAndViewContainer`** — a mutable holder passed through the whole invocation, accumulating model attributes and a view/redirect flag, plus a `requestHandled` flag. ## Step by step (invokeHandlerMethod) 1. Wrap the request: `ServletWebRequest webRequest = new ServletWebRequest(request, response)`. 2. `WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod)` — aggregates `@InitBinder` methods. 3. `ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory)`. 4. Build the invocable: `ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod)` and then **inject** the shared components: - `setHandlerMethodArgumentResolvers(this.argumentResolvers)` - `setHandlerMethodReturnValueHandlers(this.returnValueHandlers)` - `setDataBinderFactory(binderFactory)` - `setParameterNameDiscoverer(this.parameterNameDiscoverer)` 5. Create `ModelAndViewContainer mavContainer` and `modelFactory.initModel(...)` — runs `@ModelAttribute` methods, merges `@SessionAttributes`. 6. Set up async support: obtain the `WebAsyncManager`, configure `AsyncTaskExecutor` and `CallableInterceptor`s. If a concurrent result already exists (async re-dispatch), the invocable is wrapped so only the return-value handling re-runs. 7. **`invocableMethod.invokeAndHandle(webRequest, mavContainer)`** — the core call (see below). 8. If async started (`asyncManager.isConcurrentHandlingStarted()`) return `null` immediately — the request will be re-dispatched later. 9. Otherwise `return getModelAndView(mavContainer, modelFactory, webRequest)` — converts the container to a `ModelAndView`, or returns `null` if `mavContainer.isRequestHandled()` (e.g. `@ResponseBody`). ## Inside invokeAndHandle (ServletInvocableHandlerMethod) ``` Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // invokeForRequest -> getMethodArgumentValues(...) -> for each MethodParameter: // argumentResolvers.supportsParameter(p) ? resolveArgument(p, mavContainer, req, binderFactory) // then doInvoke(args) via reflection on the controller bean setResponseStatus(webRequest); // honors @ResponseStatus if (returnValue == null) { ...mark handled if not modified/status set...; return; } mavContainer.setRequestHandled(false); returnValueHandlers.handleReturnValue(returnValue, returnType, mavContainer, webRequest); ``` The return value is dispatched to the **first** `HandlerMethodReturnValueHandler` whose `supportsReturnType(returnType)` is true. For `@ResponseBody`/`@RestController` that is `RequestResponseBodyMethodProcessor`, which runs `HttpMessageConverter`s to serialize the body and sets `mavContainer.setRequestHandled(true)` so no view rendering happens. ## Gotchas - The **same composite instances** are shared across every request; only the lightweight `ServletInvocableHandlerMethod` wrapper is created per invocation — cheap. - If a parameter has no supporting resolver, you get `IllegalStateException: No suitable resolver`. If a return type has no handler, `IllegalArgumentException: Unknown return value type`. - `providedArgs` lets Spring inject already-known values (e.g. a caught exception into an `@ExceptionHandler` method). - `requestHandled == true` (set by `@ResponseBody`) is exactly why `getModelAndView` returns `null` — tying back to why `HandlerAdapter.handle` can legitimately return `null`. ## When to care Understanding this explains: why `@RequestParam`/`@RequestBody` 'just work', how to add a custom argument type, why `@ResponseBody` skips view resolution, and how async return types short-circuit the flow.

  • Why does invokeHandlerMethod create a new ServletInvocableHandlerMethod per request but reuse the argument-resolver and return-value-handler lists?
    The composites are stateless and expensive to build, so they are configured once and shared. The ServletInvocableHandlerMethod is a thin, request-scoped wrapper that binds those shared components to one specific HandlerMethod + WebDataBinderFactory for a single invocation.
  • What is the role of ModelAndViewContainer?
    It is a mutable holder passed through argument resolution, invocation, and return-value handling. It accumulates model attributes and the view/redirect decision, and its requestHandled flag tells getModelAndView whether to produce a ModelAndView (view path) or return null (response already written, e.g. @ResponseBody).

saying these in an interview costs you the question

  • Thinking a fresh set of argument resolvers/return handlers is built per request (only the wrapper is).
  • Believing @RequestBody deserialization happens in the adapter itself rather than in a HandlerMethodArgumentResolver (RequestResponseBodyMethodProcessor).
  • Assuming the method return value always becomes a view name.

context