HATEOAS: Is It Worth It?
Conference talks still treat HATEOAS as the finish line of REST maturity. Meanwhile your three client apps hard-code /orders/{id}/cancel because the URL has not changed in four years. Hypermedia-as-self-description is powerful when the server owns workflow rules that shift frequently—approval chains, compliance holds, multi-step onboarding. It is overhead when you ship the web and mobile clients yourself and deploy them lockstep with the API. The question is not "Are we RESTful enough?" but "Will links reduce coupling enough to justify larger payloads and stranger debug sessions?"
What HATEOAS actually buys you
A hypermedia response tells the client what it may do next, not just what is:
{
"id": "ord_8821",
"status": "pending_payment",
"_links": {
"self": {"href": "/orders/ord_8821"},
"pay": {"href": "/orders/ord_8821/payment-intents", "method": "POST"},
"cancel": {"href": "/orders/ord_8821/cancellations", "method": "POST"}
}
}
When status becomes shipped, the pay link disappears. Clients that respect rel types stop offering impossible actions without an app store release. Public API programs and regulated workflows benefit most.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
When to skip it
Skip full HATEOAS when you control all clients, publish OpenAPI as the contract, and version breaking URL changes rarely. Internal microservices calling each other through generated SDKs rarely follow links—they deserialize typed models and call methods. Adding _links there increases payload size and test fixtures without decoupling benefit.
Also skip it if your team will not maintain link relations with the same rigor as path versioning. Broken or stale links are worse than documented static URLs because clients trust them dynamically.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
Pragmatic middle ground
Many teams adopt partial hypermedia:
- Pagination links:
next,prev,firston collections (already standard in many APIs). - Action affordances: include
availableActions: ["cancel", "refund"]as enums for UI gating without full HAL envelopes. - Server-driven forms: return field schemas for multi-step wizards where step order changes by jurisdiction.
This captures discoverability where workflow variance is real without forcing every GET to carry link metadata.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
HAL example for a transition table
{
"order_id": "ord_8821",
"_links": {
"self": { "href": "/orders/ord_8821" }
},
"_embedded": {
"transitions": [
{
"rel": "cancel",
"href": "/orders/ord_8821/cancellations",
"method": "POST",
"title": "Cancel order"
}
]
}
}
Document rel names in your developer portal. Treat undocumented rels as private; clients must ignore unknown links per Postel's law.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
Testing and observability
Contract tests should assert required links exist for each state fixture. Log when clients request URLs that were not linked—either a rogue hard-coded path or a missing link in your state machine. Metrics on rel usage tell you which links matter before you delete legacy paths.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
Decision checklist
Adopt HATEOAS if: external integrators are primary consumers, workflow steps change without notice periods, or regulatory audit requires server-side enforcement of allowed actions. Defer if: single product squad owns stack, OpenAPI diff review catches breaking changes, and payloads are mobile-bandwidth sensitive.
Contract tests should assert required links exist for each state fixture. Log when clients request URLs that were not linked—either a rogue hard-coded path or a missing link in your state machine. Metrics on rel usage tell you which links matter before you delete legacy paths.
Treat rel values as part of your API contract. Unknown rel values must be ignored by clients. Servers may add new links without versioning when clients follow Postel's law. Breaking changes happen when you remove a link clients relied on—deprecate with sunset headers on the resource.
Embedding _links on every item in a large collection doubles payload size. Put links on collection roots when actions are uniform; per-item links when actions vary by row state. Mobile bandwidth is a real constraint on level-3 adoption.
Validate this in staging with production-like data volume before declaring done. Capture metrics baseline the week before change and compare for seven days after—subtle regressions hide in aggregates until a large tenant hits the path. Update the on-call runbook with the failure signature and rollback command so responders need not rediscover steps during an incident.
Resources
- Richardson Maturity Model Level 3 (Martin Fowler)
- HAL specification
- JSON:API links and relationships
- Siren hypermedia specification
- Roy Fielding on REST APIs must be hypermedia driven
Frequently asked questions
Is HATEOAS required for a REST API?
No. Roy Fielding's REST dissertation describes hypermedia as a constraint, but Richardson level 2 APIs with stable contracts serve most product teams well. HATEOAS pays off when you have many independent client teams or long-lived public integrations where server-driven workflow changes must not break compiled clients.
Which hypermedia format should I pick?
Pick one format—HAL, JSON:API, Siren, or Collection+JSON—and enforce it with contract tests. Mixing ad hoc _links objects with partial HAL semantics creates the worst of both worlds. JSON:API is strong if you already want compound documents; HAL is minimal and widely supported in JVM ecosystems.
How do I migrate an existing API to hypermedia?
Add links to existing responses without removing documented fields clients already use. Gate new transitions behind links first on low-risk resources like paginated lists. Measure whether any client actually follows rel types before investing in full state-machine modeling for core write flows.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →