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 Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.