Hook: Interviewers love this question because one annotation decides whether Spring sends JSON right back or starts hunting for an HTML page.
Question: What is the difference between @RestController and @Controller in Spring Boot?
Answer: @Controller is usually used for Spring MVC pages: by default, Spring treats the returned value as a view name and tries to render a template. @RestController is a shortcut for @Controller plus @ResponseBody, so every handler method writes its return value directly to the HTTP response body, usually as JSON. For REST APIs, that means less boilerplate and fewer view-resolution bugs.
Interview-Ready Answer: “I use @Controller when I want Spring MVC to return a view, like an HTML page, and I add @ResponseBody only on methods that should return raw data. @RestController is basically @Controller plus @ResponseBody on every method, so it’s the right choice for REST APIs. The big gotcha is that if I return a plain String from @Controller without @ResponseBody, Spring treats it as a view name, not text.”
Detailed Explanation: @Controller is the classic Spring MVC annotation for request-handling classes. By default, Spring assumes the returned value is meant for view rendering, so a returned String is interpreted as a logical view name. A view resolver is the part of Spring that turns that logical name into a real page, such as a Thymeleaf template. @RestController is a meta-annotation, meaning an annotation built on top of another annotation; it combines @Controller and @ResponseBody, so Spring writes every method return value directly to the HTTP response body instead of looking for a view.
DispatcherServlet, which is Spring MVC’s front controller.@GetMapping.@Controller and does not have @ResponseBody, Spring treats the return value as a view name and asks a view resolver to render HTML.@ResponseBody or lives inside @RestController, Spring skips view resolution.Accept header and the available converters.| Annotation | Default return meaning | Typical use |
|---|---|---|
@Controller | View name | HTML pages |
@Controller + @ResponseBody | Body for selected methods | Mixed MVC/API |
@RestController | Body for all methods | REST APIs |
@RestController for JSON APIs, mobile backends, microservices, and simple data endpoints.@Controller when you render pages and pass data into templates like Thymeleaf or JSP.ResponseEntity with either one when you need to set status codes, headers, or cookies explicitly.The annotation choice itself has no meaningful runtime cost; routing is effectively O(1) and the response work is usually O(n) in the size of the data being rendered or serialized. In practice, JSON serialization with Jackson is often very fast for small DTOs, usually taking only a few milliseconds or less, while template rendering adds extra view lookup and page rendering work. The biggest real cost is usually payload size, network time, and client parsing, not the annotation.
Important gotchas: a plain String from @Controller is a view name, not body text; @RestController does not mean “JSON only” because it can also return plain text or bytes; and mixed page-plus-API controllers are possible, but they are easier to misunderstand and harder to maintain.
Real-World Story: Imagine an e-commerce checkout service with a /api/orders/{id} endpoint used by a mobile app. A developer copies a page controller, forgets @ResponseBody, and returns an OrderDto from @Controller. In production, Spring tries to treat the DTO result like a view name, so the client receives an HTML error page or a 500 response instead of JSON. The mobile app then fails to parse the response, and logs show messages like “Could not resolve view” or a content-type mismatch. The fix is simple: change the class to @RestController or add @ResponseBody to the API method.
That bug is nasty because the code looks innocent, but the symptoms show up far away: users see broken screens, API consumers report JSON parsing errors, and the server logs point to view resolution rather than business logic.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
@SpringBootApplication
public class RestControllerVsControllerApplication {
public static void main(String[] args) {
SpringApplication.run(RestControllerVsControllerApplication.class, args);
}
}
// Small DTO used for JSON responses.
// A record is a compact, immutable data carrier, which fits REST responses well.
record Greeting(String message, String source) {}
@RestController
@RequestMapping("/api")
class GreetingRestController {
// With @RestController, this object is written directly to the response body.
// Jackson (the default JSON serializer in Spring Boot Web) turns the record into JSON.
@GetMapping("/greeting/{name}")
public Greeting greeting(@PathVariable String name) {
return new Greeting("Hello, " + name, "@RestController");
}
// Even a String is not a view name here. It is plain response content.
@GetMapping("/raw/{name}")
public String raw(@PathVariable String name) {
return "Hello, " + name + " (plain text body)";
}
// Edge case: reject missing input with a clear HTTP 400 response.
@GetMapping("/validated")
public Greeting validated(@RequestParam(required = false) String name) {
if (name == null || name.isBlank()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "name query parameter is required");
}
return new Greeting("Hello, " + name, "validated endpoint");
}
}
@Controller
class GreetingPageController {
// @Controller needs @ResponseBody on each method that should return data.
// This method behaves like a REST endpoint even though the class is not @RestController.
@GetMapping("/text/{name}")
@ResponseBody
public String text(@PathVariable String name) {
return "Hello, " + name + " (from @Controller + @ResponseBody)";
}
// Same idea, but returning JSON from a single method inside a regular controller.
@GetMapping("/json/{name}")
@ResponseBody
public Greeting json(@PathVariable String name) {
return new Greeting("Hello, " + name, "@Controller + @ResponseBody");
}
// Failure path: this String is treated as a view name, not response body text.
// If you do not have a template named "profile", Spring will fail during view resolution.
@GetMapping("/broken/{name}")
public String broken(@PathVariable String name) {
return "profile";
}
}
Follow-up & Tricky Questions:
@Controller over @RestController? Use @Controller when the endpoint returns HTML pages or other server-rendered views. It is the right fit for MVC screens, not pure API responses.@Controller return JSON? Yes, if you add @ResponseBody to the method or return a ResponseEntity. The class annotation alone does not force view rendering on every method.@ResponseBody do? It tells Spring to write the method return value straight to the HTTP response body. Spring then uses message converters to turn the object into JSON, text, or bytes.@RestController just syntax sugar? Yes, for most cases it is shorthand for @Controller + @ResponseBody at the class level. That shorthand is valuable because it makes REST intent obvious and reduces repeated annotations.ResponseEntity? Use it when you need control over status codes, headers, or cookies. @RestController controls how the body is written, but ResponseEntity controls the full HTTP response.@RestController method returns String, is it a view name? No. In a rest controller, that String is sent as the response body, so Spring does not try to resolve a template name.@RestController force status 200? No. A successful handler method usually ends with 200 by default, but you can return other statuses using ResponseEntity or by throwing mapped exceptions.Tricky / Gotchas:
@RestController means JSON only.” Not exactly. It means the return value goes to the body; the exact format depends on the return type and Spring’s message converters.String from @Controller is text.” Wrong by default. It is a logical view name unless you add @ResponseBody.Common Mistakes:
@Controller for REST and forgetting @ResponseBody. Correction: use @RestController for API classes or add @ResponseBody to each data-returning method.@RestController can only return JSON. Correction: it returns whatever the configured message converters can write, including text and bytes.String from @Controller and expecting text output. Correction: Spring interprets it as a view name unless you opt into body output.ResponseEntity or exception handling when you need more than the body.Memory Hook: Think: Controller hands you a ticket to a room; RestController hands you the package at the door. Ticket = view name, package = response body.
Cheat Sheet:
@Controller = Spring MVC pages by default.@RestController = @Controller + @ResponseBody.@ResponseBody skips view resolution.String return in @Controller means view name.ResponseEntity for status and headers.Practice Tasks:
@RestController endpoint that returns a DTO and test it in the browser or Postman.@Controller method that returns a String, add @ResponseBody, and observe how the behavior changes.