Step 8
Step 8 — Spring MVC request boundaries and failure contracts
0 views
Table of contents
A working controller is not yet a complete API boundary. Putting request parsing, authentication, validation, business rules, transactions, and external calls in one method mixes failure causes and test responsibilities.
Keep the controller thin
The controller translates HTTP into business input and translates the result back into HTTP. The service owns business invariants and transactions; repositories hide persistence details. Trust the authenticated principal instead of a user ID supplied by the client, and reject conflicting resource IDs in the path and body.
@PostMapping("/items")
ResponseEntity<ItemResponse> create(
@AuthenticationPrincipal UserPrincipal principal,
@Valid @RequestBody CreateItemRequest request) {
var result = itemService.create(principal.id(), request.toCommand());
return ResponseEntity.status(201).body(ItemResponse.from(result));
}
Errors are part of the API contract
Return stable, distinct error codes for validation, unauthenticated access, forbidden access, missing resources, state conflicts, and dependency timeouts. Never return stack traces or raw provider bodies. Consider idempotency or duplicate-prevention keys for writes, and put deadlines plus bounded retries around external calls.
Responsibility boundaries
HTTP mapping, validation result, status code
business invariants, authorization, transaction
SQL, external protocol, timeout
Completion evidence
- Success, validation, denial, conflict, and timeout responses are documented.
- Controller tests and real-database integration tests have separate purposes.
- A slow dependency cannot occupy request threads without a bound.
- Error responses expose no secret, SQL, or stack trace.
Related term: Spring MVC
Terms in this content
🎉 You finished Backend with Spring Boot 4
What's next? Pick another course below.