HATEOAS & Hypermedia

What HATEOAS means, a concrete linked response example, and an honest take on real-world adoption.

What HATEOAS actually means

HATEOAS — Hypermedia As The Engine Of Application State — is the constraint that gives REST its name, and the one almost every "RESTful" API in the wild quietly skips. The idea: a response shouldn't just describe a resource's current state, it should also tell the client what it can do next, as links embedded in the response itself — the same way a web page's HTML contains links to the next pages you can navigate to, instead of you needing to already know every URL on the site in advance.

Concretely: instead of a client hardcoding the knowledge "an order in pending status can be cancelled at DELETE /orders/{id}," a hypermedia-driven API includes that URL directly in the order's own representation — the client discovers what's possible by reading the response, not by consulting separate, out-of-band documentation.

A concrete example response

Compare a plain JSON response to the same resource with hypermedia controls added:

JSON
// Without HATEOAS — the client has to already know every related URL and business rule
{
  "id": 482,
  "status": "pending",
  "total": 129.99
}
JSON
// With HATEOAS — the response itself tells the client what it can do next
{
  "id": 482,
  "status": "pending",
  "total": 129.99,
  "_links": {
    "self": { "href": "/orders/482" },
    "cancel": { "href": "/orders/482", "method": "DELETE" },
    "payment": { "href": "/orders/482/payment", "method": "POST" }
  }
}

The second response encodes a real business rule directly in the data: because this order is pending, cancelling and paying are both valid next actions, so both links are present. An order that's already shipped would simply omit the cancel link entirely — a client that's actually built to rely on this can hide or disable a "Cancel" button just by checking whether _links.cancel exists, instead of independently re-implementing "which statuses are cancellable" as duplicated business logic on the client side.

JSON
// The same resource, later, once it's shipped — no cancel link is offered at all
{
  "id": 482,
  "status": "shipped",
  "total": 129.99,
  "_links": {
    "self": { "href": "/orders/482" },
    "track": { "href": "/orders/482/tracking" }
  }
}

The Richardson Maturity Model

A useful way to place any given API on a spectrum from plain RPC-over-HTTP to genuinely hypermedia-driven:

Level Characteristic Example
0 One endpoint, everything tunneled through it (classic SOAP/RPC-over-HTTP) POST /api with an action name in the body
1 Multiple resources, each with its own URL /orders/482, /users/7
2 Proper use of HTTP verbs and status codes per resource GET/POST/PATCH/DELETE on /orders/482, with correct 2xx/4xx codes
3 Hypermedia controls — responses include links describing valid next actions The _links example above

The overwhelming majority of APIs that call themselves "RESTful" — including this track's own worked examples on the introduction and status codes pages — sit comfortably at level 2 and stop there. That's not a failure to be "truly RESTful"; it's simply where the cost/benefit trade-off lands for most APIs in practice, covered next.

The honest take: how often is this actually used?

Genuinely rare, outside a small number of well-known examples (GitHub's API includes some hypermedia links; HAL and JSON:API are standardized hypermedia formats with real adoption in specific ecosystems; Spring's HATEOAS library makes it easy to bolt on for Java APIs). For the vast majority of APIs — public and internal alike — full HATEOAS isn't worth its cost, for a few concrete reasons:

  • Most API clients hardcode URLs anyway. A mobile app or frontend SPA is compiled/built against a specific API version, and its developers already know every endpoint from the documentation — dynamically discovering cancel_url from a response doesn't save them from needing to know it exists and what it means at build time.
  • It adds real payload and server-side complexity for a benefit (loose coupling to URL structure, discoverability) that mostly matters for a generic, long-lived client navigating an API it wasn't specifically built against — a genuinely rare situation outside things like RSS/Atom feed readers or a generic API browser tool.
  • Client generation tools (OpenAPI-based SDKs, covered on the OpenAPI & Documentation page) solve the same underlying problem differently — instead of discovering links at runtime, a client is generated from a spec at build time, which most teams find a better fit for how they actually build and version API clients.

The pragmatic, common industry position: know what HATEOAS is and be able to explain it clearly (it comes up constantly in interviews, precisely because it's the most misunderstood part of "REST"), but don't feel obligated to implement it fully for an internal or product API unless you have a genuinely long-lived, loosely-coupled client that would benefit from runtime discoverability — a public API with third-party integrators and infrequent client updates is the strongest case for it.

Common mistakes

  • Calling an API "RESTful" while never actually checking it against the constraints that name implies — most APIs described this way are really "level 2" on the Richardson Maturity Model (proper resources and verbs), not level 3 (hypermedia), and that's a fine, common, deliberate choice — just not the same thing as full REST as Fielding originally defined it.
  • Adding _links to every response "to be more RESTful" without any client actually using them — this is pure overhead (bigger payloads, more server-side complexity) with none of HATEOAS's actual benefit, since nothing is dynamically navigating based on those links.
  • Encoding a hypermedia link's existence as the only place a business rule lives, with no server-side enforcement — a client should never be trusted to only attempt what a _links object exposed; the server must still validate every request against the real business rules regardless of what links were or weren't shown.
  • Confusing HATEOAS with plain hyperlinking (e.g., "orderUrl": "/orders/482" pointing at a resource's own address) — that's just a convenience reference to related data, not a hypermedia control describing a valid state transition or action.

Interview questions

Q: What does HATEOAS mean, concretely? It means an API response includes links describing what actions are currently valid on that resource, so a client discovers what it can do next by reading the response itself rather than relying entirely on separate documentation. A pending order might include a cancel link that a shipped order's response simply omits, encoding a real business rule directly in the data.

Q: Where does most "RESTful" APIs actually sit on the Richardson Maturity Model, and why? Most sit at level 2 — proper resource URLs plus correct use of HTTP verbs and status codes — without reaching level 3 (hypermedia controls). This is a deliberate, pragmatic choice for most teams: full HATEOAS adds real payload and server complexity for a discoverability benefit that mostly matters to a generic, long-lived client navigating an API it wasn't specifically built against, which describes very few real API clients.