Understanding @RestController, @Controller and @ResponseBody Annotations in Spring MVC
This article explains the differences between @RestController and @Controller, shows how @RestController combines @Controller with @ResponseBody, and details the internal processing flow of the @ResponseBody annotation within Spring MVC's handler adapters and return‑value handlers.
We all know that @RestController is used for REST‑style endpoints that return data instead of a view. The source of the annotation shows that it is itself a meta‑annotation composed of @Controller and @ResponseBody :
@Target(ElementType.TYPE>
@Retention(RetentionPolicy.RUNTIME>
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any
* @since 4.0.1
*/
String value() default "";
}Because @RestController combines the semantics of both @Controller and @ResponseBody , its return values are automatically converted to JSON (or other configured formats) rather than being resolved to a view.
The processing of @ResponseBody occurs inside Spring MVC's request handling pipeline. After a request URL is mapped to a HandlerMethod , the RequestMappingHandlerAdapter creates an invocable method, injects the collection of return‑value handlers, and finally invokes the method:
// create method invocation object
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// ...
// set return‑value handlers
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// ...
// invoke method
invocableMethod.invokeAndHandle(webRequest, mavContainer);The returnValueHandlers variable holds a list of HandlerMethodReturnValueHandler implementations. Its initialization happens when the RequestMappingHandlerAdapter bean is fully constructed:
returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers)The method getDefaultReturnValueHandlers() populates this list with many built‑in handlers, among which the RequestResponseBodyMethodProcessor is responsible for @ResponseBody :
private List
getDefaultReturnValueHandlers() {
List
handlers = new ArrayList<>();
// Single‑purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters()));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation‑based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
// @ResponseBody processor
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
// Multi‑purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch‑all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
} else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}When the controller method finishes, the HandlerMethodReturnValueHandlerComposite selects an appropriate handler:
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// select a suitable handler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// handle the return value
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}The selectHandler method iterates over the registered handlers and picks the first one that supports the return type:
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// check if the handler supports the return type
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}The RequestResponseBodyMethodProcessor reports support when the method or its declaring class is annotated with @ResponseBody (or when the class is a @RestController which already includes that annotation):
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}Consequently, a controller annotated with @RestController automatically triggers the RequestResponseBodyMethodProcessor , which writes the method's return value as JSON (or another configured format) to the HTTP response.
In short, @RestController = @Controller + @ResponseBody , and the Spring MVC infrastructure processes this combination through a well‑defined chain of handler adapters and return‑value processors.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. 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.