Spring Boot REST API
RESTful endpoints, OpenAPI/SpringDoc, error model, and response shaping.
# CLAUDE.md — Spring Boot REST API
## Endpoint design
- One controller per resource. Use kebab-case URLs and stable plurals (`/users`, `/orders`, `/order-items`).
- HTTP verbs map to operations: `GET` (read), `POST` (create), `PUT` (replace), `PATCH` (partial), `DELETE` (delete).
- Use `@RestController` + the verb-specific shortcuts (`@GetMapping`, `@PostMapping`).
## DTO layer
- Don't expose JPA entities. Map to records:
```java
record UserResponse(String id, String email, Instant createdAt) {
static UserResponse from(User u) { return new UserResponse(u.getId(), u.getEmail(), u.getCreatedAt()); }
}
```
- Separate request and response DTOs. They evolve independently.
- Use **MapStruct** for tedious mapping when there's enough of it. Otherwise hand-write — clearer than reflection-based mappers.
## Validation
- Bean Validation on request DTOs. `@Valid` on the controller parameter:
```java
@PostMapping
ResponseEntity<UserResponse> create(@Valid @RequestBody CreateUserRequest req) { ... }
```
- Custom constraint annotations for cross-field rules. Don't validate in services if it could be at the DTO level.
## Error responses
- Use **Problem Details for HTTP APIs (RFC 7807)** — `ProblemDetail` is built into Spring 6.
- One `@RestControllerAdvice` per app for centralized error mapping:
```java
@ExceptionHandler(NotFoundException.class)
ProblemDetail handle(NotFoundException e) {
var pd = ProblemDetail.forStatus(404);
pd.setTitle("Not Found");
pd.setDetail(e.getMessage());
return pd;
}
```
- Don't leak stack traces to clients. Log them, return a clean problem detail.
## OpenAPI / Swagger
- **springdoc-openapi-starter-webmvc-ui** generates OpenAPI 3 from your controllers.
- Annotate when the inferred docs are wrong: `@Operation`, `@ApiResponse`, `@Schema`.
- Pin the spec version in CI — break PRs that change the API surface unintentionally.
## Pagination & filtering
- Accept `Pageable` directly. Spring binds `?page=0&size=20&sort=createdAt,desc`.
- For filtered list endpoints, use a Specification or QueryDSL — don't write `if (params.get("status") != null)` chains.
- Cap page size in code: `if (pageable.getPageSize() > 100) pageable = ...withMaxSize(100);`.
## Versioning
- URL versioning: `/api/v1/users`. Simple, cacheable, easy for clients.
- Don't version via media type unless you have a strong reason (it complicates clients).
- Keep `v1` working when you add `v2`. Deprecate explicitly via headers and changelogs.
## Idempotency
- For non-idempotent verbs (`POST`, `PATCH`), accept an `Idempotency-Key` header. Store key→result for retries.
- `PUT` should be naturally idempotent. If it isn't, you're using the wrong verb.
## Don't
- Don't return `null` from a controller. Throw a domain exception or return `ResponseEntity.notFound().build()`.
- Don't accept `Map<String, Object>` as a request body. Use a typed DTO.
- Don't put business logic in the controller. Parse → call service → return.
- Don't expose IDs you don't want enumerated. Use UUIDs for public-facing identifiers.
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.