As an architect, how do you decide between @Controller (server-rendered) and @RestController (API) styles, and what are the consequences of that choice?
answer
- who renders + who holds state
- API: stateless, JSON, CORS, tokens, ProblemDetail
- SSR: Model/flash/session, CSRF, SEO, error pages
- separate classes for clean cross-cutting config
- no runtime penalty — it's an architecture boundary
basics
~20 sUse @RestController for stateless JSON/XML APIs consumed by SPAs, mobile apps, or other services. Use @Controller with view templates when the server renders HTML directly. The choice drives your whole front-end architecture, state handling, and cross-cutting concerns.
solid answer
~40 sThe decision is really about where rendering and state live. @RestController implies a client-side or external consumer owns presentation: the server exposes stateless, content-negotiated resources (JSON via Jackson), and concerns like auth become token/cookie + CORS, errors become @RestControllerAdvice JSON payloads, and testing is MockMvc/JSON assertions. @Controller implies server-side rendering: the server owns HTML via Thymeleaf/JSP, state can flow through Model/flash attributes and sessions, errors render error pages, and SEO/first-paint are strong. Many systems mix both — page controllers plus a REST API — but I keep them in separate controller classes for clean cross-cutting config (different exception advice, security rules, content types). I also weigh HATEOAS/versioning for APIs, and CSRF/session semantics for forms. There is no runtime penalty either way; it's an architectural boundary that should match the consumer, not personal preference.
go deeper
Not expected to reason at this level.
Can state the SPA-vs-server-rendered use cases.
Discusses error handling, security, and testing differences between the two styles.
Frames it as a system boundary (state/rendering ownership), weighs coupling, caching, security surface, and keeps cross-cutting config cleanly separated.
## The real question: who renders, and who holds state `@RestController` vs `@Controller` is a proxy for a **system-architecture** decision. ### @RestController (API style) - **Consumers**: SPAs (React/Angular/Vue), mobile apps, other microservices. - **Payload**: JSON/XML via `HttpMessageConverter`s with **content negotiation** (`Accept`/`Content-Type`). - **State**: stateless; each request self-describes. Auth via bearer tokens or auth cookies; **CORS** matters for browser clients on other origins. - **Errors**: `@RestControllerAdvice` + `@ExceptionHandler` returning structured JSON (RFC 7807 `ProblemDetail` in modern Spring 6). No error *pages*. - **Evolution**: API **versioning**, backward compatibility, possibly **HATEOAS** (`spring-hateoas`), OpenAPI docs. - **Testing**: `@WebMvcTest` + `MockMvc` with JSON path assertions; contract tests. ### @Controller (server-rendered style) - **Consumers**: browsers loading full HTML pages. - **Payload**: HTML from a `ViewResolver` + template engine (Thymeleaf/JSP/FreeMarker). - **State**: `Model`, `@SessionAttributes`, flash attributes (post-redirect-get), server sessions — richer server-held state. - **Errors**: error views / `@ControllerAdvice` returning view names; friendly error pages. - **Strengths**: SEO, fast first contentful paint, simpler for form-driven CRUD apps, **CSRF** via synchronizer token in forms. - **Testing**: MockMvc asserting view name + model attributes; sometimes end-to-end browser tests. ### Mixing — and keeping it clean Real apps often need both (a rendered admin UI plus a JSON API). You *can* mix per-method `@ResponseBody` on one `@Controller`, but at scale it's better to **separate classes/packages** so that: - exception handling differs (`@RestControllerAdvice` JSON vs `@ControllerAdvice` error pages), - security config differs (stateless token filter chain for `/api/**` vs session/CSRF for pages), - content-type expectations and interceptors don't cross-contaminate. ### Consequences to weigh - **Coupling**: API style decouples front-end and back-end teams and deploy cadences; SSR couples them but simplifies the stack. - **Caching/CDN**: JSON APIs cache at the resource level; SSR pages can be edge-cached but personalized pages can't. - **Security surface**: APIs worry about CORS, token leakage; SSR worries about CSRF, XSS in templates. - **Observability**: same either way, but error *shape* differs (JSON problem details vs pages). - **No performance difference** at the annotation level — both go through the same DispatcherServlet; the cost is in rendering vs serialization, not the stereotype. ### Modern note Spring 6 / Boot 3 push `ProblemDetail` for API errors and there's renewed interest in **server-side rendering** (htmx, Thymeleaf fragments) for interactivity without a heavy SPA — so the choice isn't purely legacy-vs-modern; pick per product needs. ### Gotchas at the architecture level - Serving both HTML and JSON from the *same* URL via content negotiation is possible but often a maintenance trap; explicit separate endpoints are clearer. - Session-based auth on an API undermines statelessness and horizontal scaling; a common anti-pattern when teams copy SSR habits into `@RestController`s. - Global `@RestControllerAdvice` unintentionally catching page-controller exceptions if not scoped (use `basePackages`/`assignableTypes`).
- You inherited a @RestController API that uses HttpSession for auth state. Why is that a smell?It reintroduces server-side session state into what should be a stateless API, hurting horizontal scalability (sticky sessions or shared session store required), complicating caching, and mixing SSR habits into an API. Prefer stateless tokens or stateless auth cookies validated per request.
- How would you scope a @RestControllerAdvice so it doesn't swallow exceptions from your page @Controllers?Constrain it with @RestControllerAdvice(basePackages = "com.app.api") or assignableTypes/annotations so it only applies to the API controllers, and keep a separate @ControllerAdvice returning error views for the page controllers.
saying these in an interview costs you the question
- Claiming @RestController is 'faster' than @Controller (no runtime difference; cost is rendering vs serialization)
- Recommending session-based state for a REST API without noting the statelessness/scaling tradeoff
- Assuming SSR is always legacy and APIs always superior — it's a product/consumer decision