Spring Boot: From Basics to Production · Building REST APIs with Spring MVC
REST Controllers and Response Handling
Map HTTP endpoints, return proper status codes and headers.
@RestController classes map HTTP requests with @GetMapping, @PostMapping, @PutMapping, @PatchMapping and @DeleteMapping. Return DTOs (serialized to JSON by Jackson) or ResponseEntity when you need control over status codes and headers (e.g., 201 Created with a Location). Keep controllers thin: translate HTTP to service calls.
@RestController
@RequestMapping("/api/v1/products")
class ProductController {
private final ProductService products;
ProductController(ProductService products) { this.products = products; }
@GetMapping("/{id}")
ProductResponse get(@PathVariable long id) {
return products.get(id); // 404 handled by exception handler
}
@PostMapping
ResponseEntity<ProductResponse> create(@Valid @RequestBody CreateProductRequest request) {
ProductResponse created = products.create(request);
URI location = URI.create("/api/v1/products/" + created.id());
return ResponseEntity.created(location).body(created);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
void delete(@PathVariable long id) {
products.delete(id);
}
}
Common pitfalls
- Returning JPA entities directly (lazy-loading errors, leaking internal fields).
- Business logic in controllers.
- Always returning 200, even for creations and deletions.
Key points
- Thin controllers
- ResponseEntity for status/headers
- 201 + Location for creations
Further reading: Spring MVC: Annotated controllers
Practice questions
Why shouldn't controllers return JPA entities?
Answer
Entities may trigger lazy loading outside transactions, create infinite recursion in bidirectional relations, and expose internal fields; DTOs define a stable API contract.
How do you return 201 with a Location header?
Answer
Return ResponseEntity.created(uri).body(dto) from the POST handler.
Open in the app to mark lessons complete, take notes and track your progress.