Spring MVC is the web layer nearly every Spring Boot service exposes, so it turns up in almost every Spring Boot interview. The questions cluster around one thing: do you understand how a request travels from the socket to your controller method and back, and do you know which annotation binds which part of that request. Answering with the DispatcherServlet flow and the right binding choices is what separates someone who copied a controller from someone who understands the framework.
Walk me through what happens when a request hits a Spring MVC application.
The DispatcherServlet (the front controller) receives the request, asks a HandlerMapping which controller method handles that URL and verb, invokes it through a HandlerAdapter, and takes the return value. For an API it serializes the returned object to the response body via a message converter; for a server-rendered app it passes a view name to a ViewResolver which renders the view.
Everything routes through that single servlet — that's the front-controller pattern, and it's why cross-cutting concerns can be applied in one place. The handler mapping matches on path, method, headers and content type; the handler adapter knows how to call an @RequestMapping method, binding its arguments along the way. Understanding this pipeline is the backbone of every other MVC answer, because each annotation plugs into one stage of it.
Interview note: Follow-up: "who decides whether the response is JSON or a rendered page?" For
@ResponseBody/@RestControllermethods,HttpMessageConverters serialize the return value using content negotiation. For view-returning methods, theViewResolverpicks the template. The presence of@ResponseBodyis the fork in the road.
What is the difference between @Controller and @RestController?
@RestController is a shortcut for @Controller + @ResponseBody. A @Controller method returns a view name by default; a @RestController method returns data that is serialized straight into the response body. Use @RestController for REST APIs and @Controller when you render HTML views.
The mechanics matter: without @ResponseBody, Spring treats the returned String as a view name and hands it to the ViewResolver, which is why a REST method on a plain @Controller that returns "ok" tries to render a template called ok and 404s the view. @RestController removes that footgun for API classes by applying @ResponseBody to every method.
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public UserDto get(@PathVariable Long id) { // serialized to JSON automatically
return userService.findById(id);
}
}
Interview note: Trap: "you put a
@RestControllermethod's return type asStringand it returned the literal text, not JSON — is that a bug?" No — a rawStringis written by the string message converter astext/plain. To get a JSON string you return an object or aMap, or explicitly produceapplication/json. Knowing why reveals you understand message converters.
How do the request-mapping annotations work?
@RequestMapping maps a request to a handler by path, HTTP method, params, headers and media type. The composed shortcuts — @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping — are @RequestMapping pre-set to a verb, and are the idiomatic choice.
Class-level @RequestMapping sets a base path that method-level mappings extend, so you keep the resource prefix in one place. Beyond the verb and path you can narrow a mapping by produces and consumes (content negotiation) and by params/headers, which lets two methods share a URL but differ on what they accept or return.
@PostMapping(path = "/api/orders", consumes = "application/json",
produces = "application/json")
public ResponseEntity<OrderDto> create(@Valid @RequestBody OrderRequest req) {
OrderDto created = orderService.create(req);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
Interview note: Follow-up: "why prefer
@GetMappingover@RequestMapping(method = GET)?" It's more readable and less error-prone — the verb is part of the annotation name, so you can't forget to setmethod. They compile to the same thing; the shortcut is the convention.
@PathVariable vs @RequestParam vs @RequestBody — how do you choose?
@PathVariable binds a value that is part of the URL path (/users/42 → id = 42) and identifies a resource. @RequestParam binds a query-string or form parameter (?status=active) for filters and options. @RequestBody deserializes the entire request body — usually JSON — into an object, for create/update payloads.
The rule of thumb: path variables identify what, request params refine how, request bodies carry the data. Mixing them up is a common design smell — putting a filter in the path (/users/status/active) instead of a query param, or trying to read a large payload from query params. @RequestParam supports required and defaultValue; @PathVariable values are mandatory by nature since they're part of the route.
@GetMapping("/api/users/{id}/orders")
public List<OrderDto> orders(
@PathVariable Long id, // which user
@RequestParam(defaultValue = "ALL") String status, // filter
@RequestParam(defaultValue = "0") int page) { // option
return orderService.forUser(id, status, page);
}
Interview note: Trap: "can you have both
@RequestBodyand multiple@RequestParamon one method?" Yes, but only one@RequestBodyper method — the body is a single stream that can be read once. Multiple@RequestBodyparameters is a design error, not just a limitation.
How does content negotiation work?
Spring picks the response format by matching the request's Accept header (or a path/param strategy) against the media types each HttpMessageConverter can produce. If the client sends Accept: application/json and Jackson is on the classpath, the object is serialized as JSON; the produces attribute on the mapping constrains what a handler will emit.
In a Spring Boot API this is why returning a POJO "just becomes JSON" — Jackson's converter is auto-configured and JSON is the negotiated default. Add an XML converter and the same endpoint can serve XML when the client asks for it. The concept the interviewer wants: the controller returns an object, and a converter chosen by content negotiation decides the wire format — the controller doesn't hard-code JSON.
Interview note: Follow-up: "how do you force an endpoint to only ever return JSON?" Set
produces = MediaType.APPLICATION_JSON_VALUEon the mapping. A request with an incompatibleAcceptheader then gets 406 Not Acceptable instead of a surprise format.
How do you validate request data, and what happens on failure?
Put jakarta.validation constraints (@NotNull, @NotBlank, @Size, @Email, @Min) on the object's fields and @Valid on the controller parameter. On a violation Spring throws MethodArgumentNotValidException (for @RequestBody) before your method body runs, which you translate to a 400 with @ControllerAdvice.
The Jakarta namespace is the current detail — in Spring Boot 3 / Spring 6 the imports are jakarta.validation.*, not javax.validation.*. Validation short-circuits the handler, so your business logic never sees invalid input, and centralizing the error handling keeps every endpoint's 400 response consistent.
public record OrderRequest(
@NotBlank String sku,
@Min(1) int quantity,
@Email String customerEmail) { }
@PostMapping("/api/orders")
public OrderDto create(@Valid @RequestBody OrderRequest req) {
return orderService.create(req);
}
Interview note: Trap: "you added
@Sizebut validation never runs — why?" Usually a missing@Validon the parameter, or the validation starter isn't on the classpath (spring-boot-starter-validation). Constraints without@Valid, or without the validator on the classpath, are silently ignored — a frequent "why isn't it validating" bug.
What is ResponseEntity and when do you use it?
ResponseEntity<T> is a wrapper that lets you control the full HTTP response — status code, headers and body — instead of relying on the default 200. You use it when the status varies (201 Created, 404 Not Found, 204 No Content) or when you need to set headers like Location.
Returning a bare object always yields 200 (unless you add @ResponseStatus), which is fine for simple reads but wrong for creates and conditional responses. ResponseEntity makes the status a first-class part of the return, which is why REST-correct APIs use it for anything beyond a plain fetch.
@GetMapping("/api/users/{id}")
public ResponseEntity<UserDto> get(@PathVariable Long id) {
return userService.find(id)
.map(ResponseEntity::ok) // 200 with body
.orElseGet(() -> ResponseEntity.notFound().build()); // 404, no body
}
Interview note: Follow-up: "
ResponseEntityvs@ResponseStatus— when each?"@ResponseStatussets a fixed status for a method or exception type;ResponseEntitysets it dynamically per call. Fixed outcome → annotation; outcome depends on the result →ResponseEntity.
Interceptors vs filters — what's the difference and when do you use each?
A servlet Filter runs at the servlet-container level, wrapping the request before and after Spring MVC processes it — ideal for concerns that don't need MVC context, like logging, compression or CORS. A HandlerInterceptor is Spring-MVC-aware and runs around handler execution, with preHandle, postHandle and afterCompletion hooks that can see the resolved handler, the model and any exception.
Choose by what you need access to. If you only touch raw request/response, a filter is simpler and runs earlier. If you need the matched handler, the model, or to short-circuit based on controller-level metadata (say, a custom auth annotation on the handler method), an interceptor is the right layer because it executes inside Spring MVC where that context exists.
@Component
public class AuditInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
MDC.put("route", req.getRequestURI());
return true; // false short-circuits the request before the controller
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
MDC.clear();
}
}
Interview note: Trap: "which runs first, filter or interceptor?" The filter — it's outside the DispatcherServlet, so it wraps the entire dispatch, including the interceptor chain. Order: filter → DispatcherServlet → interceptor.preHandle → controller → interceptor.postHandle → interceptor.afterCompletion → filter.
How do you handle exceptions thrown by controllers?
Centralize them with @ControllerAdvice (or @RestControllerAdvice) plus @ExceptionHandler methods. A single advice class catches exceptions from every controller and maps each type to a clean HTTP response, so error formatting lives in one place instead of being duplicated in try/catch blocks.
This keeps controllers focused on the happy path. A @RestControllerAdvice with an @ExceptionHandler(MethodArgumentNotValidException.class) turns validation failures into a consistent 400 body across the whole API, and a handler for your domain's NotFoundException maps it to 404. It's the standard Spring MVC answer to "where does error handling go."
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<ApiError> notFound(NoSuchElementException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiError("NOT_FOUND", e.getMessage()));
}
}
Interview note: Follow-up: "how do you keep this consistent across many services?" Extend
ResponseEntityExceptionHandlerfor the framework exceptions and share a base advice, or standardize on RFC 7807ProblemDetail, which Spring 6 supports natively for a uniform error shape.
What interviewers really test
Spring MVC questions check that you can trace a request end-to-end and pick the right tool at each stage: the correct binding annotation, the right validation wiring, ResponseEntity where status matters, and interceptors versus filters chosen by the context they need. Vague answers ("the controller handles it") don't survive the first follow-up about the DispatcherServlet or message converters.
Build the mechanics through the Spring Boot learning path, then round out the web layer with the two topics MVC always drags in: exception handling, since real controllers must fail cleanly, and Spring Data JPA, since most controllers ultimately read and write a database. When you can narrate a request from DispatcherServlet to JSON response and back, a Spring MVC mock interview stops being a memory test and becomes a walk through code you understand.
Frequently Asked Questions
What is the DispatcherServlet in Spring MVC?
What's the difference between @Controller and @RestController?
When do I use @PathVariable versus @RequestParam?
How do I validate incoming request data in Spring MVC?
What's the difference between a HandlerInterceptor and a servlet Filter?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Java Full Stack program

