Spring BootREST Principles
Spring Boot

REST Principles

REST (Representational State Transfer) is an architectural style for distributed hypermedia systems, defined by Roy Fielding in 2000. It is not a protocol or standard — it is a set of six constraints that, when applied to an HTTP API, produce systems that are scalable, stateless, and independently evolvable. Understanding these constraints is the foundation for designing Spring Boot APIs that behave predictably and integrate cleanly with any HTTP client.

What REST Is

REST stands for Representational State Transfer. Roy Fielding defined it in his 2000 doctoral dissertation as an architectural style — a coordinated set of constraints applied to the design of a distributed hypermedia system. REST is not a specification, a protocol, or a library. There is no REST schema validator and no REST wire format. An API is RESTful to the degree that it satisfies the constraints. REST is commonly conflated with "JSON over HTTP", but JSON is not required and HTTP is not mandated (though it is the universal transport in practice). What distinguishes a RESTful API from an arbitrary HTTP API is adherence to the six architectural constraints — and most importantly, the uniform interface constraint, which governs how clients and servers interact through resources, representations, and hypermedia.

The Six REST Constraints

Fielding defined six constraints. The first five are required; the sixth (code on demand) is optional. Violating a required constraint means the system is not RESTful — it may still be a useful HTTP API, but it does not carry the properties REST provides.
Java
// 1. CLIENT-SERVER
//    Separation of concerns between UI (client) and data storage (server).
//    Client and server evolve independently. The server does not know or
//    care about the client's rendering technology.

// 2. STATELESS
//    Each request from client to server must contain all information needed
//    to understand and process the request. The server holds no client
//    session state between requests. Session state lives entirely on the client.
//    Consequence: any server instance can handle any request — horizontal
//    scaling requires no session affinity.

// 3. CACHEABLE
//    Responses must label themselves as cacheable or non-cacheable.
//    When cacheable, clients and intermediaries (CDNs, proxies) may reuse
//    the response for equivalent later requests.
//    Improves: efficiency, scalability, perceived performance.

// 4. UNIFORM INTERFACE
//    The central REST constraint — four sub-constraints:
//      a) Resource identification in requests (URI identifies the resource)
//      b) Resource manipulation through representations (client holds enough
//         info to modify or delete the resource)
//      c) Self-descriptive messages (each message includes enough info to
//         describe how to process it — Content-Type, status codes, etc.)
//      d) Hypermedia as the engine of application state (HATEOAS) — responses
//         include links to available next actions

// 5. LAYERED SYSTEM
//    A client cannot tell whether it is connected directly to the origin server
//    or to an intermediary (load balancer, CDN, API gateway, cache).
//    Layers may be added or removed without affecting the client.

// 6. CODE ON DEMAND (optional)
//    Servers may extend client functionality by transferring executable code
//    (JavaScript, applets). Rarely applied in modern REST APIs.

Resources and URIs

In REST, a resource is any named information: a user, an order, a collection of products, a PDF invoice. URIs identify resources — not actions, not operations. This is the most commonly violated REST principle in practice: RPC-style APIs name endpoints after verbs (getUser, createOrder), while REST names them after nouns (users, orders) and uses HTTP methods to express the action.
Shell
# ── RPC-style (not RESTful) — verbs in URIs: ─────────────────────────
GET  /getUser?id=42
POST /createOrder
POST /deleteProduct?id=7
GET  /getUserOrders?userId=42

# ── RESTful — nouns in URIs, HTTP method expresses the action: ─────────
GET    /users/42          # fetch user 42
POST   /users             # create a new user
PUT    /users/42          # replace user 42
PATCH  /users/42          # partially update user 42
DELETE /users/42          # delete user 42
GET    /users/42/orders   # fetch orders belonging to user 42

# URI design rules:
# - Use nouns, never verbs
# - Use plural nouns for collections (/users, /orders, /products)
# - Use lowercase and hyphens, never camelCase or underscores
#   CORRECT:   /shipping-addresses
#   INCORRECT: /shippingAddresses, /shipping_addresses
# - Hierarchy expresses containment or relationship
#   /users/{userId}/orders/{orderId}
# - Avoid deep nesting beyond 2-3 levels — flatten with query params
#   AVOID:   /users/42/orders/7/items/3/reviews
#   PREFER:  /order-items/3/reviews  or  /reviews?orderId=7

# Query parameters are for filtering, sorting, pagination — not identity:
GET /products?category=electronics&sort=price&order=asc&page=2&size=20
GET /orders?status=pending&userId=42&from=2024-01-01

HTTP Methods and Their Semantics

REST leverages HTTP methods (verbs) to express the intent of an operation on a resource. Each method has defined semantics — safe, idempotent, or neither — and using them correctly allows clients, caches, and infrastructure to make correct assumptions about your API's behavior.
Shell
# Safe: does not modify server state. GET and HEAD are safe.
# Idempotent: calling N times produces the same result as calling once.
#             GET, HEAD, PUT, DELETE, OPTIONS are idempotent.
# Neither:    POST and PATCH are neither safe nor idempotent.

# ── GET — retrieve a resource or collection ────────────────────────────
GET /users/42          # 200 OK + user body, or 404 Not Found
GET /users             # 200 OK + array of users
GET /users?role=admin  # 200 OK + filtered array
# Safe + idempotent. Must not modify state. Response may be cached.

# ── POST — create a new resource ──────────────────────────────────────
POST /users            # 201 Created + Location: /users/43
POST /orders           # 201 Created + new order body
# Neither safe nor idempotent. Calling twice creates two resources.
# Response: 201 Created with Location header pointing to the new resource.

# ── PUT — replace a resource entirely ─────────────────────────────────
PUT /users/42          # 200 OK or 204 No Content
# Idempotent. Client sends the complete representation.
# If /users/42 does not exist, may return 404 or create it (document your choice).
# Calling PUT /users/42 with the same body N times: same result each time.

# ── PATCH — partial update ─────────────────────────────────────────────
PATCH /users/42        # 200 OK or 204 No Content
# Not idempotent in general (though implementations often are).
# Client sends only the fields to change — server merges them.

# ── DELETE — remove a resource ────────────────────────────────────────
DELETE /users/42       # 204 No Content (or 200 OK with body)
# Idempotent. Deleting an already-deleted resource should return 404
# (or 204 again — document your choice and be consistent).

# ── HEAD — same as GET but no response body ───────────────────────────
HEAD /users/42         # 200 OK, headers only — used to check existence

# ── OPTIONS — discover allowed methods ────────────────────────────────
OPTIONS /users/42      # 200 OK, Allow: GET, PUT, PATCH, DELETE
# Used by browsers for CORS preflight checks.

HTTP Status Codes

Status codes communicate the result of an operation semantically — clients, CDNs, and monitoring tools all interpret them. Using the correct code is as important as using the correct HTTP method. The most common mistake is returning 200 OK for everything and encoding errors in the response body.
Shell
# ── 2xx Success ───────────────────────────────────────────────────────
200 OK              # GET, PUT, PATCH succeeded with a response body
201 Created         # POST succeeded — include Location header
202 Accepted        # Request accepted for async processing (not yet complete)
204 No Content      # DELETE or PUT succeeded — no body to return

# ── 3xx Redirection ───────────────────────────────────────────────────
301 Moved Permanently   # resource has a new permanent URI
304 Not Modified        # cached response is still valid (ETag / If-None-Match)

# ── 4xx Client Errors ─────────────────────────────────────────────────
400 Bad Request         # malformed request, validation failure, invalid JSON
401 Unauthorized        # authentication required (misleadingly named)
403 Forbidden           # authenticated but not authorized
404 Not Found           # resource does not exist
405 Method Not Allowed  # HTTP method not supported for this URI
409 Conflict            # state conflict (duplicate email, optimistic lock failure)
410 Gone                # resource permanently deleted (stronger than 404)
415 Unsupported Media Type  # Content-Type not accepted
422 Unprocessable Entity    # well-formed request but semantic validation failed
429 Too Many Requests       # rate limit exceeded

# ── 5xx Server Errors ─────────────────────────────────────────────────
500 Internal Server Error   # unhandled exception — something broke server-side
502 Bad Gateway             # upstream service returned an invalid response
503 Service Unavailable     # server overloaded or down for maintenance
504 Gateway Timeout         # upstream service timed out

# ── Common mistakes ───────────────────────────────────────────────────
# WRONG: 200 OK with { "error": "User not found" } in the body
# RIGHT: 404 Not Found with a structured error body

# WRONG: 200 OK for a resource creation
# RIGHT: 201 Created with Location: /users/43

# WRONG: 500 for a validation error caused by bad client input
# RIGHT: 400 Bad Request or 422 Unprocessable Entity

Representations and Content Negotiation

A resource and its representation are distinct concepts. The resource is the abstract information (user 42); the representation is a concrete encoding of its current state (a JSON document, an XML document, a CSV row). The same resource may have multiple representations. Content negotiation lets clients request the format they prefer.
Shell
# Client signals preferred response format via Accept header:
GET /users/42
Accept: application/json          # wants JSON

GET /users/42
Accept: application/xml           # wants XML

GET /reports/2024-q4
Accept: text/csv                  # wants CSV

GET /invoices/99
Accept: application/pdf           # wants PDF

# Server signals the format it returned via Content-Type header:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

# Client signals the format of the request body via Content-Type:
POST /users
Content-Type: application/json

{
  "name": "Alice",
  "email": "alice@example.com"
}

# If the server cannot produce the requested format:
HTTP/1.1 406 Not Acceptable

# If the server cannot parse the request body format:
HTTP/1.1 415 Unsupported Media Type

# Spring Boot handles content negotiation automatically via
# HttpMessageConverters — add Jackson for JSON, JAXB for XML,
# and the correct converter activates based on Accept / Content-Type.

Statelessness in Practice

Statelessness is the REST constraint with the most direct impact on scalability. The server must not store any client session state between requests. Every request must be self-contained. This means authentication credentials (JWT, API key) travel with every request — not in a server-side session.
Shell
# ── Stateful (not RESTful) — server session holds auth state: ─────────
POST /login               # server creates session, returns session cookie
GET  /profile             # server looks up session to identify the caller
POST /logout              # server destroys session

# Problem: every request must hit the same server instance (session affinity).
# Horizontal scaling requires sticky sessions or a shared session store.

# ── Stateless (RESTful) — client carries all state: ───────────────────
POST /auth/token          # client authenticates, server returns a JWT
GET  /profile             # client sends JWT in every request
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

# The server validates the JWT on each request — no session lookup.
# Any server instance can handle any request.
# Scaling is trivial — add instances freely.

# ── What "state" means: ───────────────────────────────────────────────
# Session state (NOT on server):  who is logged in, what page they are on
# Resource state (ON server):     the user record, the order in the database
# Application state (on client):  pagination cursor, selected filters, cart items

# ── Pagination — stateless approach: ──────────────────────────────────
# WRONG (stateful): server stores "user 42 is on page 3"
# RIGHT (stateless): client sends the page in every request
GET /products?page=3&size=20

# Cursor-based pagination (more stable than page numbers):
GET /products?cursor=eyJpZCI6MTAwfQ&size=20
# Response includes the next cursor:
{
  "data": [...],
  "nextCursor": "eyJpZCI6MTIwfQ",
  "hasMore": true
}

Idempotency and Safety

Two properties of HTTP methods — safety and idempotency — determine how clients, proxies, and infrastructure may treat requests. Understanding them prevents subtle bugs in retry logic, caching, and error recovery.
Shell
# ── Safe: no observable side effects ─────────────────────────────────
# Safe methods: GET, HEAD, OPTIONS
# A safe method may be retried freely, prefetched, or cached.
# Browsers retry safe requests automatically after a network error.

GET /users/42    # safe — retry freely, may be cached

# ── Idempotent: N identical calls = same result as one call ───────────
# Idempotent methods: GET, HEAD, PUT, DELETE, OPTIONS
# Calling the same idempotent request twice produces the same server state.
# Clients may safely retry idempotent requests after a timeout.

DELETE /users/42    # idempotent — first call deletes, second call: 404
                    # server state after both: user 42 is gone

PUT /users/42       # idempotent — replacing with the same body N times:
                    # server state is the same after each call

# ── Neither safe nor idempotent ───────────────────────────────────────
POST /orders        # calling twice creates two orders
PATCH /cart/items/5 # PATCH increments quantity: calling twice doubles the increment

# ── Practical implications ────────────────────────────────────────────

# Retry logic:
# - Safe/idempotent (GET, PUT, DELETE): retry automatically on network timeout
# - POST/PATCH: do NOT retry automatically — may create duplicates

# Making POST idempotent with idempotency keys:
POST /orders
Idempotency-Key: a8098c1a-f86e-11da-bd1a-00112444be1e
# Server stores the key. If the same key is seen again within a window,
# return the original response instead of creating a second order.
# Stripe, PayPal, and most payment APIs require this pattern.

# DELETE idempotency — two valid approaches (document your choice):
# Approach 1: DELETE /users/42 returns 404 after first deletion
# Approach 2: DELETE /users/42 always returns 204 (more idempotent-friendly)

HATEOAS — Hypermedia as the Engine of Application State

HATEOAS is the most advanced and least commonly implemented REST constraint. A fully RESTful API embeds links in responses that tell the client what actions are available next — rather than the client having hardcoded knowledge of the API's URI structure. The client starts from a single known entry point and discovers everything else through hypermedia controls.
Shell
# Without HATEOAS — client has hardcoded knowledge of every URI:
GET /orders/42
{
  "id": 42,
  "status": "pending",
  "total": 99.99
}
# Client must know: to cancel, call DELETE /orders/42
#                   to pay, call POST /payments with orderId: 42
#                   to view items, call GET /orders/42/items

# With HATEOAS — server tells the client what it can do next:
GET /orders/42
{
  "id": 42,
  "status": "pending",
  "total": 99.99,
  "_links": {
    "self":   { "href": "/orders/42" },
    "cancel": { "href": "/orders/42", "method": "DELETE" },
    "pay":    { "href": "/payments",  "method": "POST" },
    "items":  { "href": "/orders/42/items" }
  }
}

# After payment, the available actions change — cancel is gone:
GET /orders/42
{
  "id": 42,
  "status": "paid",
  "total": 99.99,
  "_links": {
    "self":    { "href": "/orders/42" },
    "invoice": { "href": "/invoices/42" },
    "items":   { "href": "/orders/42/items" }
  }
}

# Spring HATEOAS provides EntityModel, CollectionModel, and WebMvcLinkBuilder
# to generate these link structures automatically in Spring Boot:

EntityModel<Order> model = EntityModel.of(order,
    linkTo(methodOn(OrderController.class).getOrder(order.getId())).withSelfRel(),
    linkTo(methodOn(PaymentController.class).pay(order.getId())).withRel("pay"),
    linkTo(methodOn(OrderController.class).getItems(order.getId())).withRel("items")
);

Richardson Maturity Model

The Richardson Maturity Model (RMM) provides a practical scale for measuring how RESTful an HTTP API is. It is not a formal standard but a widely-used heuristic for understanding where an API sits on the spectrum from plain HTTP to fully RESTful.
Shell
# ── Level 0 — The Swamp of POX ───────────────────────────────────────
# One URI, one HTTP method (always POST), RPC-style payload.
# HTTP is used purely as a transport tunnel.

POST /api
{ "action": "getUser", "id": 42 }

POST /api
{ "action": "createOrder", "userId": 42, "items": [...] }

# Examples: SOAP, XML-RPC, early REST misconceptions.

# ── Level 1 — Resources ───────────────────────────────────────────────
# Multiple URIs — one per resource. Still uses only POST.
# Resources are identified, but HTTP semantics are not used.

POST /users/42
{ "action": "get" }

POST /orders
{ "action": "create", "items": [...] }

# ── Level 2 — HTTP Verbs ──────────────────────────────────────────────
# Resources + correct HTTP methods + correct status codes.
# This is what most "REST APIs" implement in practice.

GET    /users/42          # 200 OK
POST   /orders            # 201 Created
DELETE /orders/7          # 204 No Content
GET    /users/99          # 404 Not Found

# ── Level 3 — Hypermedia Controls (HATEOAS) ───────────────────────────
# Resources + HTTP verbs + links embedded in responses.
# Clients discover available actions from the response — no hardcoded URIs.
# This is Fielding's definition of REST.

GET /orders/42
{
  "id": 42,
  "status": "pending",
  "_links": {
    "self":   { "href": "/orders/42" },
    "cancel": { "href": "/orders/42", "method": "DELETE" },
    "pay":    { "href": "/payments/new?orderId=42", "method": "POST" }
  }
}

# Most production APIs operate at Level 2 and call themselves RESTful.
# Level 3 is the ideal — and is required for Fielding's strict definition.
# Choosing Level 2 is a pragmatic trade-off, not a failure —
# document it honestly rather than overclaiming "fully RESTful".