Backend Development 7 min read

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.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding @RestController, @Controller and @ResponseBody Annotations in Spring MVC

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.

backendJavaannotationSpring MVCResponseBodyRestController
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.