How does @ResponseBody behave at the class level versus the method level, and how do you return both views and JSON from one controller?
answer
- @Target TYPE + METHOD
- class-level = all methods (that's @RestController)
- method-level = just that handler
- mix: @Controller + per-method @ResponseBody
- view from RestController -> ModelAndView
basics
~20 sAt class level, @ResponseBody applies to every handler method (that's what @RestController does). At method level, it applies only to that one method. So on a plain @Controller you put @ResponseBody on just the JSON methods and leave the rest returning view names.
solid answer
~40 s@ResponseBody is a @Target TYPE + METHOD annotation. On a method it makes only that handler serialize its return value through the HttpMessageConverter chain. On a class, it applies to all handler methods — which is exactly how @RestController works, since @RestController is meta-annotated with @ResponseBody. To mix behaviors, use a plain @Controller and annotate only the API methods with @ResponseBody; unannotated methods keep returning logical view names resolved by a ViewResolver. You cannot 'turn off' @ResponseBody for one method of a @RestController — it's all-body. If a @RestController method needs to actually render a view, you'd return a ModelAndView or View object explicitly, or better, split the concerns into separate controllers. ResponseEntity works from either stereotype and bypasses view resolution regardless.
code
java · 17 lines@Controller
class MixedController {
// Returns a rendered HTML page
@GetMapping("/dashboard")
String dashboard(Model model) {
model.addAttribute("stats", loadStats());
return "dashboard"; // view name
}
// Same class, returns JSON — method-level @ResponseBody
@GetMapping("/api/stats")
@ResponseBody
Stats stats() {
return loadStats();
}
}go deeper
Knows @ResponseBody exists and roughly what it does.
Should nail the scope difference (class vs method) and the mix pattern on a plain @Controller.
Explains why mixing is a design smell and how ModelAndView/ResponseEntity behave orthogonally.
Connects the pattern to @RestControllerAdvice and content-negotiation/converter architecture.
## Where @ResponseBody can go `@ResponseBody` is declared with `@Target({ElementType.TYPE, ElementType.METHOD})`, so it is valid on **both** classes and methods. ### Method level Placing `@ResponseBody` on a single handler method makes *that method* write its return value directly to the response body via an `HttpMessageConverter`. Other methods in the same class are unaffected. ```java @Controller class MixedController { @GetMapping("/dashboard") String dashboard(Model model) { // view name -> template model.addAttribute("stats", loadStats()); return "dashboard"; } @GetMapping("/api/stats") @ResponseBody Stats stats() { // serialized to JSON return loadStats(); } } ``` This is the canonical way to serve **both** an HTML page and a JSON endpoint from one class. ### Class level Put `@ResponseBody` on the class and **every** handler method inherits body serialization. That is precisely how `@RestController` is defined: ```java @Controller @ResponseBody public @interface RestController { ... } ``` So `@RestController` ≡ `@Controller` + class-level `@ResponseBody`. There is no per-method opt-out: once the class carries `@ResponseBody`, a returned `String` is written as the raw body, never a view name. ### Rendering a view from a @RestController If a `@RestController` method genuinely needs to render a template, returning a `String` won't do it (it becomes body text). You must return a **`ModelAndView`** or a resolved `View`, which the framework handles specially and *not* via the message converters. In practice, mixing is a smell — keep page controllers and API controllers separate. ### ResponseEntity is orthogonal `ResponseEntity<T>` always writes the body via converters and lets you set status and headers, independent of whether the class is `@Controller` or `@RestController`. It effectively behaves like `@ResponseBody` for that method plus header/status control. ### Gotchas - Adding `@ResponseBody` to a method whose return type is `String` but you *wanted* a view — now you've broken view rendering for that route. - On a `@RestController`, forgetting that `String` returns are literal bodies (with `text/plain` via `StringHttpMessageConverter`) — not JSON-quoted unless you return an object/collection. - `@RestControllerAdvice` = `@ControllerAdvice` + `@ResponseBody`, the same composition pattern applied to exception handlers.
- How is @RestControllerAdvice related to this?It's the same composition trick for exception handling: @RestControllerAdvice = @ControllerAdvice + @ResponseBody, so @ExceptionHandler methods serialize their return values into the response body (JSON error payloads) instead of resolving views.
- On a @RestController, how would you actually render a Thymeleaf template if you had to?Return a ModelAndView (or an explicit View), which the DispatcherServlet renders through the ViewResolver — it isn't passed to the HttpMessageConverters. Returning a String would just write the string as the body.
saying these in an interview costs you the question
- Claiming you can put @ResponseBody on a method to opt OUT of body serialization in a @RestController
- Thinking class-level and method-level @ResponseBody behave differently in *how* they serialize (they don't — only in *scope*)
- Believing a String from @RestController is auto-wrapped in JSON quotes (it's raw text/plain)