Spring MVC Rules
Controllers, ResponseEntity, validation, exception handling, and content negotiation.
# CLAUDE.md — Spring MVC
## Controllers
- `@RestController` for JSON APIs. `@Controller` only when you actually return view names.
- Constructor-inject services. Final fields, no `@Autowired`.
- Map URLs at the class level: `@RequestMapping("/users")`. Methods use the verb-specific shortcuts (`@GetMapping`, `@PostMapping`).
- One controller per resource. Cross-cutting endpoints (`/health`, `/metrics`) live in their own controller.
## Request handling
- `@RequestBody` for JSON bodies. Add `@Valid` to trigger Bean Validation.
- `@PathVariable` and `@RequestParam` are explicit — name them with the parameter name when it differs:
```java
@GetMapping("/{id}") UserDto get(@PathVariable("id") String userId)
```
- Use `Optional<T>` for `@RequestParam(required = false)` instead of nullable defaults.
## Response shaping
- Return `ResponseEntity<T>` when you need to set status, headers, or vary by branch:
```java
return ResponseEntity.created(uri).body(dto);
```
- Return the DTO directly when status is always 200. Spring fills in the rest.
- Set `produces = MediaType.APPLICATION_JSON_VALUE` when you mean it. Avoid `MediaType.ALL_VALUE` on data endpoints.
## Validation
- Bean Validation on the request DTO with `jakarta.validation.constraints.*`:
```java
record CreateUser(@NotBlank @Email String email, @NotBlank @Size(min = 8) String password) {}
```
- `@Valid` on the controller parameter. Spring throws `MethodArgumentNotValidException` on failure.
- For groups (`Default`, `OnCreate`, `OnUpdate`), use `@Validated(OnCreate.class)`.
## Exception handling
- `@RestControllerAdvice` class with `@ExceptionHandler` methods. One per error type.
- Map domain exceptions to status codes here, never in controllers:
```java
@ExceptionHandler(NotFoundException.class)
ResponseEntity<ProblemDetail> handle(NotFoundException e) {
return ResponseEntity.status(404).body(ProblemDetail.forStatusAndDetail(404, e.getMessage()));
}
```
- Use `ProblemDetail` (RFC 7807) for error responses. It's the modern Spring default.
## Content negotiation
- Default to JSON. Add XML/Other media types only when a client actually needs them.
- Don't use `produces` to "version" APIs (`application/vnd.app.v1+json`). Use URL versioning for clarity.
## Filters & interceptors
- Servlet filters for cross-cutting on the request boundary (request id, logging).
- `HandlerInterceptor` for Spring-aware concerns (auth, MDC).
- Don't put business logic in filters. They're for cross-cutting only.
## Don't
- Don't return `null` from a controller. Use `ResponseEntity.notFound().build()` or throw.
- Don't catch `Exception` in a controller method to translate it. Use `@ExceptionHandler` so the same logic applies app-wide.
- Don't expose entity classes from controllers. Map to DTOs.
- Don't mix `@RequestParam` and `@PathVariable` carelessly — they bind from different sources and the bug is silent.
Other Spring templates
Spring Framework Core Rules
Dependency injection, bean lifecycle, configuration classes, and profiles.
Spring Data JPA
Repositories, derived queries, custom JPQL, projections, and N+1 prevention.
Spring Security Rules
SecurityFilterChain (no WebSecurityConfigurerAdapter), OAuth2 resource server, JWT.
Spring WebFlux (Reactive)
Reactive Spring with Mono/Flux, R2DBC, backpressure, and non-blocking patterns.
Spring Boot 3 Modern Rules
Boot 3+ defaults: starters, configuration properties, profiles, and structured logging.