How does HAL-FORMS differ from HAL, and how does Spring HATEOAS decide which one to render?
answer
- HAL = _links; HAL-FORMS = _links + _templates
- prs.hal-forms+json is a superset
- _templates: method + contentType + properties
- chosen by Accept header / content negotiation
- same assembler code, converter differs
basics
~10 sHAL describes navigation with _links. HAL-FORMS is a superset that also adds _templates describing how to perform write operations (method + properties). Spring picks one via content negotiation on the request's Accept header.
solid answer
~40 sHAL (`application/hal+json`) is read-oriented: `_links` (relation→href) plus `_embedded`. HAL-FORMS (`application/prs.hal-forms+json`) is a strict superset that keeps `_links` but adds a `_templates` object describing writable operations — each template has an HTTP `method`, a `contentType`, and a `properties` array (name, required, regex, readOnly). Those templates come from the affordances you attached to links. Spring HATEOAS chooses the representation by *content negotiation*: it matches the request's `Accept` header against the enabled hypermedia types. Enable them with `spring-boot-starter-hateoas` (both HAL and, when requested, HAL-FORMS are supported) or `@EnableHypermediaSupport(type = { HAL, HAL_FORMS })`. So a client asking for `application/hal+json` gets links only; a client asking for `application/prs.hal-forms+json` additionally gets `_templates`. The same controller/assembler code serves both — you don't branch on media type yourself.
code
java · 11 lines// Enabling both (non-Boot). Boot's starter does this for you.
@Configuration
@EnableHypermediaSupport(type = {
EnableHypermediaSupport.HypermediaType.HAL,
EnableHypermediaSupport.HypermediaType.HAL_FORMS
})
class HypermediaConfig { }
// Same handler, two wire formats depending on Accept:
// Accept: application/hal+json -> { ..., "_links": {...} }
// Accept: application/prs.hal-forms+json -> { ..., "_links": {...}, "_templates": {...} }go deeper
Knows HAL-FORMS adds forms/_templates on top of HAL.
Understands the two media types and that write descriptions live in _templates.
Explains content negotiation selecting the converter and the superset relationship, plus enabling both types.
Decides when runtime-discoverable write contracts (HAL-FORMS) justify their complexity vs. HAL + external schema docs.
**Two media types, one hierarchy.** - **HAL** — `application/hal+json` (`MediaTypes.HAL_JSON`). Reserved properties `_links` and `_embedded`. Purely about *navigation*: it tells a client which related resources exist and where. It says nothing about how to modify state. - **HAL-FORMS** — `application/prs.hal-forms+json` (`MediaTypes.HAL_FORMS_JSON`). A superset: everything HAL has, plus a `_templates` object. Each template describes an *operation*: - `method` — the HTTP verb (`post`, `put`, `patch`, `delete`). - `contentType` — expected request body media type (defaults to `application/json`). - `properties` — an array describing each input field: `name`, `required`, `readOnly`, `regex`, `prompt`, `value`, etc. - `target` (optional) — an override URI if the action targets a different href than the enclosing link. The default, unnamed template is keyed `default`. **Where `_templates` comes from.** You don't author `_templates` by hand. They are generated from **affordances** (`afford(...)`) attached to links, whose properties/validation are inferred from the afforded controller method's `@RequestBody` type and Bean Validation annotations. No affordances ⇒ HAL-FORMS still renders, but `_templates` will only contain whatever the framework can infer (often just a default template or nothing beyond links). **How Spring chooses — content negotiation.** Spring MVC performs standard `Accept`-header content negotiation. Spring HATEOAS registers message converters for each *enabled* hypermedia type. The client's `Accept` decides: - `Accept: application/hal+json` → HAL converter → `_links`/`_embedded` only (affordances dropped). - `Accept: application/prs.hal-forms+json` → HAL-FORMS converter → adds `_templates`. - No/`*/*` accept → the server's default/most-specific enabled type. The controller returns the same `EntityModel`/`CollectionModel`; the *converter* chosen by negotiation determines the wire format. You should not inspect the media type and branch manually. **Enabling.** With Boot's `spring-boot-starter-hateoas`, HAL is on by default and HAL-FORMS is available. In plain Spring, `@EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.HAL_FORMS })` registers both. Ordering in the array can influence the default when the client is unspecific. **Gotchas.** - A common bug: 'my affordances don't show' → the client didn't send the HAL-FORMS `Accept` header, so the HAL converter ran and dropped them. - HAL-FORMS is a superset, so a HAL-only client can safely consume a HAL-FORMS-capable server by asking for `application/hal+json` — nothing breaks. - The HAL-FORMS media type string is `application/prs.hal-forms+json` (the `prs.` prefix denotes a personal/vanity tree registration); mis-typing it means no negotiation match and you fall back to another type. - HAL-FORMS `properties` support extra UI-oriented hints (`prompt`, `placeholder`, `options`) in newer Spring HATEOAS versions, useful for driving generated UIs. **When to use which.** Use HAL for read-mostly APIs or when clients are hand-written and know how to write. Use HAL-FORMS when you want clients (or a generic hypermedia UI) to *discover write operations at runtime* — the server owns the write contract, and clients build forms dynamically, improving evolvability.
- Can a HAL-only client safely call a HAL-FORMS-capable endpoint?Yes. HAL-FORMS is a superset; the client requests `application/hal+json` and gets `_links` only. Nothing breaks because negotiation serves the HAL representation.
- Do you write different controller code for HAL vs HAL-FORMS?No. You return the same `RepresentationModel`; the message converter selected by `Accept`-header negotiation decides the format. Branching on media type in the controller is an anti-pattern.
saying these in an interview costs you the question
- Saying you must branch on the `Accept` header inside the controller.
- Believing HAL-FORMS replaces/breaks HAL rather than being a superset.
- Getting the media type wrong (it's `application/prs.hal-forms+json`).
- Thinking `_templates` must be written by hand instead of derived from affordances.