How does HAL-FORMS differ from plain HAL, and how do you enable it in Spring Data REST?
answer
- HAL-FORMS = HAL + _templates (affordances)
- MediaTypes.HAL_FORMS_JSON / application/prs.hal-forms+json
- _templates: method + properties(required/readOnly/regex)
- enable via Accept header (content negotiation)
- or setDefaultMediaType(HAL_FORMS_JSON)
basics
~20 sHAL-FORMS extends HAL by adding a _templates section describing how to write (which HTTP method, which fields, required/read-only). You enable it by content negotiation (Accept: application/prs.hal-forms+json) or by setting it as the default media type.
solid answer
~40 sPlain HAL (application/hal+json) tells a client where it can go via _links and _embedded, but not how to construct a write request. HAL-FORMS (application/prs.hal-forms+json) adds a _templates object describing affordances: for each named template the HTTP method, the target, and a list of properties with metadata like required, readOnly, regex, and prompt. This lets a generic client render a form or validate input without out-of-band knowledge. In Spring Data REST it is served through content negotiation — a client sends Accept: application/prs.hal-forms+json and the framework emits _templates derived from the repository's exposed methods and entity metadata. Alternatively set it globally with RepositoryRestConfiguration.setDefaultMediaType(MediaTypes.HAL_FORMS_JSON). Spring HATEOAS models affordances via the Affordance API and HAL-FORMS message converters.
code
java · 13 lines// Option A: opt-in per request via content negotiation
// GET /books/1 Accept: application/prs.hal-forms+json
// -> body includes _links, _embedded AND _templates
// Option B: make it the global default
@Component
class HalFormsConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config, CorsRegistry cors) {
config.setDefaultMediaType(MediaTypes.HAL_FORMS_JSON);
}
}go deeper
Aware HAL-FORMS adds form/affordance info beyond plain HAL.
Names the _templates block and the media type, knows the Accept-header route.
Contrasts content negotiation vs global default and explains where template metadata is derived from.
Reasons about client-adaptability, schema evolution, and the blast radius of changing the default media type across consumers.
**HAL** is a *read-oriented* hypermedia format: `_links` and `_embedded` describe navigation and inlined data, but a client still has to *know* — out of band — that to create a book it must POST a JSON with `title` and `author` fields. **HAL-FORMS** closes that gap by describing **affordances** (available actions and their inputs) in-band. **Media type:** `application/prs.hal-forms+json` (constant `MediaTypes.HAL_FORMS_JSON` in Spring HATEOAS). It is a strict superset of HAL — same `_links`/`_embedded` — plus a **`_templates`** object. **The `_templates` block:** keyed by template name (`default` for the primary action), each entry contains: - `method` — the HTTP method (e.g. `PUT`, `POST`). - `target` — optional URL to submit to (defaults to the resource's self). - `contentType` — expected request body type. - `properties` — an array describing each field: `name`, `required`, `readOnly`, `regex` (validation pattern), `prompt` (human label), `type`, and sometimes `options`. Example: ```json "_templates": { "default": { "method": "PUT", "properties": [ { "name": "title", "required": true, "prompt": "Title" }, { "name": "published", "required": false, "readOnly": true } ] } } ``` A generic HAL-FORMS client can now render an edit form and validate before submitting — no hardcoded knowledge of the schema. **Enabling in Spring Data REST — two approaches:** 1. **Content negotiation (preferred):** the client sends `Accept: application/prs.hal-forms+json`. Spring Data REST already registers the HAL-FORMS message converter, so it responds with `_templates` automatically. This keeps HAL as the default and serves HAL-FORMS only to clients that ask. 2. **Global default:** `config.setDefaultMediaType(MediaTypes.HAL_FORMS_JSON)` inside a `RepositoryRestConfigurer` makes *every* response HAL-FORMS regardless of `Accept`. In plain Spring HATEOAS (non-Data-REST) you enable it with `@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)` and build affordances via the `Affordances`/`afford(...)` API on links. **Where the metadata comes from:** Spring Data REST derives `_templates` from the repository's exposed HTTP methods and the entity's Jackson/Bean metadata; Bean Validation annotations (`@NotNull`, `@Size`, `@Pattern`) can surface as `required`/`regex` hints. **Gotchas:** - HAL-FORMS is a *superset*; a HAL-only client can still read the `_links`/`_embedded` and ignore `_templates`. - Switching the global default media type affects all consumers and tooling; content negotiation is safer for mixed clients. - `_templates` reflects only what the API exposes — if you disabled DELETE/PUT via the exposure DSL, corresponding affordances won't appear. - The `prs.` in the media type stands for *personal/vanity tree* (an IANA registration category), not a typo. **When to use:** when you want *self-describing writes* — generic UIs, API explorers, or clients that adapt to schema changes without redeployment. If clients are hand-written and already know the payloads, plain HAL suffices.
- What lives in the _templates block that HAL lacks?A description of write affordances: for each template, the HTTP method, target, and a properties list carrying metadata like required, readOnly, regex, and prompt — so a client knows how to construct a valid write request.
- Why prefer content negotiation over setDefaultMediaType for HAL-FORMS?Content negotiation serves HAL-FORMS only to clients that send the matching Accept header, leaving HAL as the default for everyone else; a global default changes every response and can surprise existing consumers and tooling.
saying these in an interview costs you the question
- Saying HAL already contains _templates
- Thinking HAL-FORMS replaces _links/_embedded rather than adding to them
- Claiming you must write custom controllers to emit _templates
- Assuming the global default is the only way to enable it