REST API Design Fundamentals
Design stable resource APIs with correct HTTP semantics, validation, pagination, idempotency, errors, compatibility, and OpenAPI documentation.
Learning objectives
- Map resource operations to HTTP method and status semantics
- Design compatible DTO, error, pagination, and idempotency contracts
What it is
REST API design is the deliberate use of HTTP resource identifiers, methods, representations, and response semantics to create a contract that clients can understand and evolve against. REST does not require one universal response envelope or CRUD-only behavior.
Why it matters
Clients build retry, cache, validation, authorization, and error-handling logic around the contract. Ambiguous status codes, unstable JSON, offset-only pagination, or undocumented failure shapes turn implementation details into long-lived integration risk.
How it works
| Operation | Method | Typical success |
|---|---|---|
| List tasks | GET /v1/tasks | 200 with page |
| Create task | POST /v1/tasks | 201 + Location |
| Read task | GET /v1/tasks/{id} | 200 or 404 |
| Replace task | PUT /v1/tasks/{id} | 200/204 |
| Partial update | PATCH /v1/tasks/{id} | 200/204 |
| Delete task | DELETE /v1/tasks/{id} | 204 (idempotent effect) |
Simple example
{
"items": [
{ "id": 42, "title": "Review settlement", "status": "OPEN" }
],
"nextCursor": "eyJpZCI6NDJ9",
"hasMore": true
}A cursor should be opaque to clients and derived from a stable, unique ordering. If ordering is by created_at, use a unique tie-breaker such as id.
Backend example
@PostMapping
ResponseEntity<TaskResponse> create(
@Valid @RequestBody CreateTaskRequest request,
@RequestHeader(value = "Idempotency-Key", required = false) String key) {
TaskResponse created = taskService.create(request, key);
URI location = URI.create("/v1/tasks/" + created.id());
return ResponseEntity.created(location).body(created);
}
public record CreateTaskRequest(
@NotBlank @Size(max = 160) String title,
@FutureOrPresent Instant dueAt) {}
{
"type": "https://henheang.site/problems/task-title-invalid",
"title": "Task validation failed",
"status": 400,
"detail": "One or more fields are invalid.",
"instance": "/v1/tasks",
"errors": [{ "field": "title", "code": "NotBlank" }],
"traceId": "7fbc..."
}Production example
- 1
Compatibility
Add optional response fields freely when clients ignore unknown fields; do not silently change meaning, type, enum policy, or nullability.
- 2
Idempotency
Scope a client key to actor and operation, hash the relevant request, atomically persist outcome, and reject key reuse with a different request.
- 3
Errors
Use stable machine codes, human-safe details, trace correlation, and documented status mappings. RFC 9457 obsoletes RFC 7807.
- 4
Documentation
Keep OpenAPI request, success, error, auth, pagination, and idempotency examples executable in contract tests where practical.
Common mistakes
- Verbs and database table names in every URI
- Returning 200 with success=false for protocol errors
- Leaking stack traces or SQL messages
- Using page numbers without a stable order
- Accepting arbitrary sort columns directly into SQL
- Breaking clients by renaming fields or narrowing accepted values
Best practices
- Model external DTOs separately from persistence models
- Use explicit limits and allowlists for filter/sort/search input
- Return 201 and Location for created resources when applicable
- Design retry and idempotency behavior together
- Document timezone and null/omission rules
- Treat OpenAPI as a reviewed contract, not decoration
Trade-offs
| Decision | Advantage | Cost |
|---|---|---|
| Offset pagination | Simple random page access | Degrades and shifts under writes |
| Keyset pagination | Stable and efficient continuation | No arbitrary page jump; stable sort required |
| URI version | Very visible compatibility boundary | Multiple routes and duplicated docs |
| Compatible evolution | Fewer version branches | Requires strict change discipline |
Interview questions
- When should an API return 400, 401, 403, 404, 409, or 422?
- How do PUT and PATCH differ?
- How would you implement idempotent payment creation?
- Why can keyset pagination outperform offset?
- How would you evolve an enum safely?
Hands-on task
- 1
Contract
Write OpenAPI for create, read, list, update, and delete Task operations, including every error response.
- 2
Policy
Define stable order, cursor encoding, maximum page size, allowed filters, and idempotency-key behavior.
- 3
Verify
Create integration tests for validation, conflict, unknown resource, duplicate idempotency key, and backward-compatible JSON.
References
- IETF: RFC 9110 — HTTP Semantics — https://www.rfc-editor.org/rfc/rfc9110
- IETF: RFC 9457 — Problem Details for HTTP APIs — https://www.rfc-editor.org/rfc/rfc9457
- OpenAPI Initiative: OpenAPI Specification 3.2.0 — https://spec.openapis.org/oas/v3.2.0.html
- Spring: Annotated Controllers — https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html
Retrieval check
Before you continue
- Explain the main idea in your own words without rereading the page.
- Name one production problem this knowledge helps prevent.
- Describe where you would apply it in a Spring Boot project.
Source metadata
Primary references checked on 2026-07-16. Version scope is stated at the top of this page.