Designing REST APIs that don’t fight you
A few hard-won habits for Spring Boot APIs that stay easy to use as the system grows.
Over a few years of backend work — HR workflow automation, an enterprise platform, an insurance core — I’ve built and maintained a lot of REST APIs in Spring Boot. The ones that aged well all shared a handful of habits. None of them are clever; they just spare future-you a lot of pain.
Model resources, not actions
It’s tempting to expose endpoints that mirror your code: /createInvoice,
/sendReminder. They feel natural for a week and fight you forever after. Model
things and let HTTP verbs do the work:
POST /invoices
GET /invoices/{id}
POST /invoices/{id}/reminders
When in doubt, ask “what is the noun?” — the verbs are already standardised.
Make errors boringly predictable
Clients spend more time handling your failures than your happy path. Pick one
error shape and return it everywhere — a code, a human message, and a field map
for validation problems — and use real status codes (400, 404, 409, not
200 with {"error": ...}). In Spring, a single @RestControllerAdvice keeps
this consistent so no controller has to think about it.
Validate at the edge, fail fast
Bad data should never make it past the controller. Bean Validation
(@Valid, @NotNull, @Size) on request DTOs means the rest of your code can
trust its inputs. The deeper invalid data travels, the more expensive and
confusing the eventual failure.
Don’t leak your entities
Returning JPA entities straight from a controller couples your API to your database schema and invites lazy-loading surprises. A small DTO layer is a little more typing now and a lot more freedom later — you can evolve the table without breaking every client.
Page anything that can grow
A list endpoint that returns “all” of something is a production incident waiting for the table to get big. Page from day one, even when there are three rows in the demo.
Most API pain isn’t exotic — it’s a hundred small inconsistencies that each seemed fine in isolation. Decide these things once, enforce them in one place, and the API mostly stops fighting you.