Spring MVC Source Code Walkthrough: Understanding How @ResponseBody Returns JSON
This article walks through the Spring MVC internals, explaining how the @ResponseBody annotation triggers JSON serialization by tracing the DispatcherServlet, HandlerAdapters, and ReturnValueHandlers, and includes runnable Spring Boot test code to illustrate the processing flow.
Author: Tianlu (company code name), currently responsible for Java backend development at Beike Zhaofang.
Background – During a learning exchange with a colleague about Spring MVC internals, the question arose: what class interprets the @ResponseBody annotation, why does it cause a JSON string to be returned, and how does this differ from returning a ModelAndView? This article follows the source‑code tracing process to answer those questions.
Test Code
@RestController
@EnableAutoConfiguration
@ComponentScan
@SpringBootApplication
@EnableAsync
public class BasicApplication {
private static final Logger logger = LoggerFactory.getLogger(BasicApplication.class);
public static void main(String[] args) {
SpringApplication application = new SpringApplication(BasicApplication.class);
application.run(args);
}
@RequestMapping("/")
public String index() {
return "Welcome!";
}
}3. Source Tracing
3.1 DispatcherServlet (core of Spring MVC) – The entry method doService calls doDispatch , which performs four main steps: obtain the Handler, obtain the HandlerAdapter, invoke the actual request handling, and process the result (including exception handling).
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
// 1. Get HandlerExecutionChain from handlerMappings
mappedHandler = getHandler(processedRequest);
// 2. Get HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. Invoke the handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 4. Process the result and handle exceptions
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}The handlerMappings and handlerAdapters are initialized in DispatcherServlet by overriding onRefresh in its superclass FrameworkServlet .
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
// initialize handlerMappings
initHandlerMappings(context);
// initialize handlerAdapters
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}3.2 AbstractHandlerMethodAdapter – The handle method in this abstract class delegates to handleInternal , which is implemented by concrete subclasses.
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}3.3 RequestMappingHandlerAdapter – Extends AbstractHandlerMethodAdapter and implements three key methods: handleInternal , invokeHandlerMethod , and getModelAndView . The handleInternal method ultimately returns the ModelAndView produced by invokeHandlerMethod .
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter {
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// ...
ModelAndView mav = invokeHandlerMethod(request, response, handlerMethod);
return mav; // may be null when the request is handled directly
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// ...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
if (mavContainer.isRequestHandled()) {
return null;
}
// ...
}
}3.4 ServletInvocableHandlerMethod – Called by RequestMappingHandlerAdapter to actually invoke the controller method and handle the return value.
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// ...
mavContainer.setRequestHandled(false);
// ...
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}3.5 HandlerMethodReturnValueHandlerComposite – Iterates over registered HandlerMethodReturnValueHandler instances to find one that supports the current return type. In this example the selected handler is RequestResponseBodyMethodProcessor .
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
// ...
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}3.6 RequestResponseBodyMethodProcessor – Handles methods annotated with @ResponseBody . Its supportsReturnType checks for the annotation on the class or method.
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}When invoked, it marks the request as handled and writes the return value using message converters, which perform the actual JSON serialization.
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}3.7 AbstractMessageConverterMethodProcessor – The base class that actually delegates to an HttpMessageConverter (e.g., MappingJackson2HttpMessageConverter) to write the JSON payload to the response.
protected
void writeWithMessageConverters(T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
}
}In summary, the crucial step that turns a controller return value into JSON is the selection of RequestResponseBodyMethodProcessor by the HandlerMethodReturnValueHandlerComposite . The whole flow demonstrates Spring's use of the Template Method pattern and the Chain of Responsibility pattern, both worth studying for backend developers.
Thank you for reading.
Beike Product & Technology
As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.
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.