backend / rest apis / 07_richardson_maturity_hateoas.md

Richardson Maturity Model and HATEOAS

6 interview angles 5 min read source

Richardson Maturity Model and HATEOAS

The most-discussed and least-applied REST theory. Roy Fielding’s REST dissertation (2000) defined a strict interpretation; almost nobody fully implements it. The Richardson Maturity Model (Leonard Richardson, 2008) describes how close an API gets.

The four levels

Level Means
Level 0 one URL, one HTTP method (often POST), payload contains everything (RPC over HTTP)
Level 1 many URLs (resources), but still one HTTP method usually
Level 2 proper HTTP verbs (GET/POST/PUT/PATCH/DELETE) + status codes
Level 3 HATEOAS — responses include links to next actions

Most “REST APIs” in production are Level 2. Level 3 is rare. Fielding considered only Level 3 actual REST; everything else is just “HTTP API.”

Level 0 — RPC over HTTP

POST /service HTTP/1.1
Content-Type: application/json

{"action": "getUser", "id": 42}

One URL. Action in the body. SOAP and XML-RPC live here.

Level 1 — Resources

POST /users HTTP/1.1
{"action": "get", "id": 42}

Now there’s a URL per resource type but methods are still ignored.

Level 2 — HTTP verbs and status codes

GET /users/42           → 200 OK + user data
POST /users             → 201 Created + Location header
PUT /users/42           → 200 OK
DELETE /users/42        → 204 No Content
GET /users/99           → 404 Not Found
POST /users             → 422 (validation failed)

The conventional “RESTful API.” Resources as URLs, verbs as actions, status codes as outcomes. This is what most teams mean by REST.

Level 3 — HATEOAS

Hypermedia As The Engine Of Application State. The response tells the client what they can do next.

GET /users/42

{
  "id": 42,
  "name": "Alice",
  "balance": "100.00",
  "_links": {
    "self": {"href": "/users/42"},
    "deposit": {"href": "/users/42/deposit"},
    "withdraw": {"href": "/users/42/withdraw"},
    "close-account": {"href": "/users/42/close"}
  }
}

The client doesn’t need to know URL patterns. It navigates by following links from the entry point. Server can change URLs freely; client only depends on link relations.

If the account is overdrawn, the server might omit the withdraw link — the available actions are part of the response.

Why HATEOAS hasn’t won

In theory: clients are loosely coupled, URLs are server-controlled, the API self-describes.

In practice:

  • Clients hardcode URL patterns anyway (it’s faster).
  • Frontend devs need to know what actions exist to design UI; the runtime hypermedia tells them too late.
  • OpenAPI/Swagger documentation solves most “discoverability” needs already.
  • The extra payload size + complexity isn’t worth it for typical CRUD.

HATEOAS shows up in some niches: HAL, JSON:API, Spring HATEOAS. For the median API, Level 2 is plenty.

HAL — Hypertext Application Language

The most-implemented HATEOAS format:

{
  "id": 42,
  "name": "Alice",
  "_links": {
    "self": {"href": "/users/42"},
    "orders": {"href": "/users/42/orders"},
    "manager": {"href": "/users/7"}
  },
  "_embedded": {
    "orders": [
      {
        "id": 1001,
        "_links": {"self": {"href": "/orders/1001"}}
      }
    ]
  }
}

_links for navigation, _embedded for inlined related resources (avoids extra round trips for known-needed relations).

Standardized as application/hal+json.

JSON:API

More opinionated:

{
  "data": {
    "type": "user",
    "id": "42",
    "attributes": {"name": "Alice", "balance": "100.00"},
    "relationships": {
      "orders": {
        "links": {"related": "/users/42/orders"},
        "data": [{"type": "order", "id": "1001"}]
      }
    },
    "links": {"self": "/users/42"}
  },
  "included": [
    {"type": "order", "id": "1001", "attributes": {...}}
  ]
}

Verbose but consistent across APIs. Has a sparse fieldsets (?fields[user]=name), filtering (?filter[status]=active), and pagination spec built in.

Use when: building a public API with many clients; want a standard most JSON:API tooling can read.

REST’s real constraints (per Fielding)

Beyond the maturity model, REST as defined has six constraints. Most APIs violate at least a few:

Constraint Means Real-world adherence
Client-server separation of UI and data universal
Stateless each request stands alone (no server-side session) partial — many APIs use sessions
Cacheable responses indicate cacheability partial — most ignore caching
Layered system client can’t tell if it’s talking to proxies universal
Uniform interface resources, verbs, status codes, HATEOAS partial — HATEOAS rare
Code on demand (optional) server can send code to client almost never

“Stateless” doesn’t mean “no auth” — it means the server doesn’t hold per-client session state. Auth tokens (JWT) are fine; opaque session IDs that require server lookup technically violate.

In practice: nobody cares. “REST” colloquially means “HTTP + JSON + resources + verbs.”

Where HATEOAS actually pays off

  • Long-lived APIs with many client types where versioning is painful (banking, government). The hypermedia evolves; clients follow.
  • Workflow APIs where state machines determine available actions. The next-step links match the current state.
  • APIs with many resource types and complex navigation — a HATEOAS client can crawl.

For most B2B SaaS and consumer apps: Level 2 + OpenAPI is more practical.

How to argue for/against in an interview

For:

  • Self-describing — clients don’t hardcode URLs.
  • Workflow APIs where actions are state-dependent.
  • Long-term API evolution.

Against:

  • Most clients hardcode URLs anyway.
  • Payload bloat.
  • Tooling poorer than for plain REST + OpenAPI.
  • Frontend devs need design-time knowledge, which HATEOAS doesn’t help.

Common interview confusions

  • “REST means HATEOAS.” — Fielding said so; the industry didn’t follow. “REST” colloquially is Level 2.
  • “Level 3 is always better.” — better in some dimensions (loose coupling), worse in others (payload size, client complexity).
  • “JSON:API is a HATEOAS implementation.” — supports it; not all JSON:API responses include actionable links.

Interview angle

  • “What’s the Richardson Maturity Model?” — four levels: 0=RPC-over-HTTP, 1=resources, 2=HTTP verbs + status codes (the common “RESTful”), 3=HATEOAS (hypermedia links in responses).
  • “What level is most APIs at?” — Level 2. Level 3 is the strict-REST ideal but rare in practice.
  • “What is HATEOAS?” — Hypermedia As The Engine Of Application State. Responses include links to follow for next actions. Clients navigate the API by relation names, not hardcoded URLs.
  • “Why isn’t HATEOAS universal?” — clients prefer hardcoded URLs (less work). OpenAPI solves the discoverability problem at design time without the runtime cost. The benefits don’t justify the complexity for most CRUD APIs.
  • “What’s HAL vs JSON:API?” — both add hypermedia structure to JSON responses. HAL is simpler (_links, _embedded); JSON:API is more opinionated (type/id/attributes/relationships, mandatory envelope).
  • “What does Fielding consider ‘real’ REST?” — Level 3, plus all six constraints (stateless, cacheable, uniform interface, layered, etc.). Most “REST APIs” violate at least the stateless and HATEOAS constraints.