All posts

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.

  • #java
  • #spring-boot
  • #backend
  • #api

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.