Spring MVC & REST APIs
Spring MVC is the web layer most Java backends are built on.
Introduction
Spring MVC is the web layer most Java backends are built on. It maps HTTP requests to Java methods, converts bodies to objects (and back), validates input, handles exceptions and renders responses — JSON, HTML or anything else.
Despite its name (MVC = Model-View-Controller, from the old server-rendered-pages era), today's Spring MVC is mostly used to build REST APIs that return JSON.
Understanding the topic
The request flow:
- An HTTP request hits the DispatcherServlet.
- A HandlerMapping chooses the controller method based on URL, HTTP verb, headers, etc.
- Argument resolvers convert headers, path variables and JSON bodies into method parameters.
- The controller runs and returns a value (a DTO, a
ResponseEntity, a view name…). - A HttpMessageConverter (e.g. Jackson) serialises the response.
- Exception handlers turn any thrown exception into an appropriate HTTP status.
Syntax reference
A small REST controller:
@RestController@RequestMapping("/api/orders")public class OrderController {private final OrderService orders;public OrderController(OrderService orders) {this.orders = orders;}@GetMapping("/{id}")public OrderView get(@PathVariable Long id) {return orders.find(id);}@GetMappingpublic Page<OrderView> list(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "20") int size) {return orders.list(PageRequest.of(page, size));}@PostMapping@ResponseStatus(HttpStatus.CREATED)public OrderView create(@Valid @RequestBody NewOrder body) {return orders.place(body);}}
Informative example
Centralised exception → HTTP mapping with @ControllerAdvice:
@RestControllerAdviceclass ApiExceptionHandler {@ExceptionHandler(OrderNotFoundException.class)@ResponseStatus(HttpStatus.NOT_FOUND)ApiError notFound(OrderNotFoundException ex) {return new ApiError("ORDER_NOT_FOUND", ex.getMessage());}@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)ApiError validation(MethodArgumentNotValidException ex) {var fields = ex.getFieldErrors().stream().map(f -> f.getField() + ": " + f.getDefaultMessage()).toList();return new ApiError("VALIDATION_FAILED", String.join(", ", fields));}}record ApiError(String code, String message) {}
Real-world use
Almost every JSON API written in Java in the last decade uses Spring MVC under the hood. The reactive counterpart, WebFlux, exists for high-concurrency event-driven services, but MVC remains the right default for 95% of apps.
Best practices
- Return DTOs, not JPA entities — your API contract and your schema should evolve separately.
- Use
@ControllerAdvicefor centralised exception → HTTP status mapping. - Validate input at the boundary with
@Validand Bean Validation annotations. - Version your API (
/api/v1/...) before you need to.
Common mistakes
- Doing business logic inside the controller — keep controllers thin, push logic into services.
- Returning sensitive fields (passwords, internal IDs) because you serialised the entity directly.
- Forgetting CORS configuration when the frontend lives on a different origin.
Hands-on exercise
Build it: create a /api/books endpoint with full CRUD: list (paginated), get-by-id, create (with validation), update, delete. Add a @ControllerAdvice that returns a clean JSON error body for 404 and 400. Test each endpoint with curl or your favourite HTTP client.