skip to content

How does HAL-FORMS differ from plain HAL, and how do you enable it in Spring Data REST?

level: seniorimportance: should knowfreq 35%

answer

  1. HAL-FORMS = HAL + _templates (affordances)
  2. MediaTypes.HAL_FORMS_JSON / application/prs.hal-forms+json
  3. _templates: method + properties(required/readOnly/regex)
  4. enable via Accept header (content negotiation)
  5. or setDefaultMediaType(HAL_FORMS_JSON)

basics

~20 s

HAL-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 s

Plain 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
java
// 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

for a junior

Aware HAL-FORMS adds form/affordance info beyond plain HAL.

for a middle

Names the _templates block and the media type, knows the Accept-header route.

for a senior

Contrasts content negotiation vs global default and explains where template metadata is derived from.

for a principal

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

context