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<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
