All Spring templates

Spring Boot REST API

RESTful endpoints, OpenAPI/SpringDoc, error model, and response shaping.

DevZone Tools2,880 copiesUpdated Apr 2, 2026Spring BootSpringJava
# 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 Spring templates