Spring MVC¶
Spring MVC implements the Model-View-Controller pattern for web applications. The DispatcherServlet is the front controller that routes requests to @Controller/@RestController methods. Key annotations: @RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestBody, @ResponseBody. @RestController = @Controller + @ResponseBody. Supports exception handling via @ControllerAdvice.
Request Flow¶
Flow: Client → DispatcherServlet → HandlerMapping (finds controller) → HandlerAdapter (invokes method) → Controller → response. For @RestController, the return object is serialized to JSON via Jackson's HttpMessageConverter. For @Controller, a ViewResolver resolves the view name to a template.
Deep Dive: Processing Steps
Client → DispatcherServlet → HandlerMapping → Controller
↓
Client ← ViewResolver ← View ← Model (or @ResponseBody → JSON)
- Request arrives at
DispatcherServlet - HandlerMapping finds the controller method
- HandlerAdapter invokes it
- Controller returns model + view name (or response body)
- ViewResolver resolves view (if applicable)
- View renders response → sent to client
With @RestController: steps 5-6 skipped, Jackson serializes to JSON.
Controller Annotations¶
@RestController = @Controller + @ResponseBody. Use @GetMapping, @PostMapping, @PutMapping, @DeleteMapping for HTTP methods. Extract data with: @PathVariable (URL path), @RequestParam (query string), @RequestBody (JSON body), @RequestHeader (headers). Use @Valid to trigger bean validation.
Deep Dive: Full CRUD Example
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<User> getAllUsers() { ... }
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) { ... }
@GetMapping("/search")
public List<User> search(
@RequestParam String name,
@RequestParam(defaultValue = "0") int page) { ... }
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@Valid @RequestBody CreateUserRequest request) { ... }
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) { ... }
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) { ... }
}
| Annotation | Purpose |
|---|---|
@PathVariable |
Extract from URL path /users/{id} |
@RequestParam |
Extract query param ?name=John |
@RequestBody |
Deserialize JSON body |
@RequestHeader |
Extract HTTP header |
@ResponseStatus |
Set HTTP status code |
@Valid |
Trigger bean validation |
Request/Response Body & Jackson¶
Jackson (included with spring-boot-starter-web) handles JSON ↔ Java serialization. Use record DTOs for request/response objects. Validate input with @NotBlank, @Email, @Min annotations. Customize Jackson with @JsonFormat, @JsonInclude, or a custom ObjectMapper bean.
Deep Dive: DTOs and Jackson Config
Exception Handling with @ControllerAdvice¶
@RestControllerAdvice provides global exception handling across all controllers. Use @ExceptionHandler(ExceptionType.class) methods to handle specific exceptions and return consistent error responses. This centralizes error logic instead of repeating try-catch in every controller.
Deep Dive: Global Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return new ErrorResponse("VALIDATION_FAILED", errors.toString());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneral(Exception ex) {
log.error("Unexpected error", ex);
return new ErrorResponse("INTERNAL_ERROR", "Something went wrong");
}
}
public record ErrorResponse(String code, String message) {}
Filters vs Interceptors¶
Filters operate at the Servlet level (before/after DispatcherServlet) — used for logging, CORS, security. Interceptors operate at the Spring MVC level (within DispatcherServlet) — used for auth, localization, have full access to Spring context. Filters see all requests; interceptors only see DispatcherServlet requests.
Deep Dive: Examples
Filter:
@Component
public class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
log.info("Request: {} {}", request.getMethod(), request.getRequestURI());
chain.doFilter(request, response);
log.info("Response: {}", response.getStatus());
}
}
Interceptor:
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res,
Object handler) {
return true; // Return false to block request
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**");
}
}
| Feature | Filter | Interceptor |
|---|---|---|
| Level | Servlet | Spring MVC |
| Spring context | Limited | Full access |
| Applied to | All requests | DispatcherServlet only |
ResponseEntity¶
ResponseEntity<T> gives full control over the HTTP response — status code, headers, and body. Use it when you need to return different status codes conditionally (e.g., 200 vs 404), set custom headers, or return 201 Created with a Location header.
Deep Dive: Usage Patterns
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest req) {
User user = userService.create(req);
URI location = URI.create("/api/users/" + user.getId());
return ResponseEntity.created(location).body(user);
}
Common Interview Questions¶
Common Interview Questions
- What is the DispatcherServlet?
- Explain the request processing flow in Spring MVC.
- What is the difference between
@Controllerand@RestController? - What is the difference between
@PathVariableand@RequestParam? - How does
@RequestBodywork? - How do you handle exceptions globally in Spring MVC?
- What is
@ControllerAdvice? - What is the difference between a Filter and an Interceptor?
- What is
ResponseEntityand when would you use it?