4. Media types

4.1. HAL – Hypertext Application Language

JSON Hypertext Application Language or HAL is one of the simplest and most widely adopted hypermedia media types adopted when not discussing specific web stacks.spring-doc.cn

It was the first spec-based media type adopted by Spring HATEOAS.spring-doc.cn

4.1.1. Building HAL representation models

As of Spring HATEOAS 1.1, we ship a dedicated HalModelBuilder that allows to create RepresentationModel instances through a HAL-idiomatic API. These are its fundamental assumptions:spring-doc.cn

  1. A HAL representation can be backed by an arbitrary object (an entity) that builds up the domain fields contained in the representation.spring-doc.cn

  2. The representation can be enriched by a variety of embedded documents, which can be either arbitrary objects or HAL representations themselves (i.e. containing nested embeddeds and links).spring-doc.cn

  3. Certain HAL specific patterns (e.g. previews) can be directly used in the API so that the code setting up the representation reads like you’d describe a HAL representation following those idioms.spring-doc.cn

Here’s an example of the API used:spring-doc.cn

// An order
var order = new Order(…); (1)

// The customer who placed the order
var customer = customer.findById(order.getCustomerId());

var customerLink = Link.of("/orders/{id}/customer") (2)
  .expand(order.getId())
  .withRel("customer");

var additional = …

var model = HalModelBuilder.halModelOf(order)
  .preview(new CustomerSummary(customer)) (3)
  .forLink(customerLink) (4)
  .embed(additional) (5)
  .link(Link.of(…, IanaLinkRelations.SELF));
  .build();
1 We set up some domain type. In this case, an order that has a relationship to the customer that placed it.
2 We prepare a link pointing to a resource that will expose customer details
3 We start building a preview by providing the payload that’s supposed to be rendered inside the _embeddable clause.
4 We conclude that preview by providing the target link. It transparently gets added to the _links object and its link relation is used as the key for the object provided in the previous step.
5 Other objects can be added to show up under _embedded. The key under which they’re listed is derived from the objects relation settings. They’re customizable via @Relation or a dedicated LinkRelationProvider (see Using the LinkRelationProvider API for details).
{
  "_links" : {
    "self" : { "href" : "…" }, (1)
    "customer" : { "href" : "/orders/4711/customer" } (2)
  },
  "_embedded" : {
    "customer" : { … }, (3)
    "additional" : { … } (4)
  }
}
1 The self link as explicitly provided.
2 The customer link transparently added through ….preview(…).forLink(…).
3 The preview object provided.
4 Additional elements added via explicit ….embed(…).

In HAL _embedded is also used to represent top collections. They’re usually grouped under the link relation derived from the object’s type. I.e. a list of orders would look like this in HAL:spring-doc.cn

{
  "_embedded" : {
    "order : [
      … (1)
    ]
  }
}
1 Individual order documents go here.

Creating such a representation is as easy as this:spring-doc.cn

Collection<Order> orders = …;

HalModelBuilder.emptyHalDocument()
  .embed(orders);

That said, if the order is empty, there’s no way to derive the link relation to appear inside _embedded, so that the document will stay empty if the collection is empty.spring-doc.cn

If you prefer to explicitly communicate an empty collection, a type can be handed into the overload of the ….embed(…) method taking a Collection. If the collection handed into the method is empty, this will cause a field rendered with its link relation derived from the given type.spring-doc.cn

HalModelBuilder.emptyHalModel()
  .embed(Collections.emptyList(), Order.class);
  // or
  .embed(Collections.emptyList(), LinkRelation.of("orders"));

will create the following, more explicit representation.spring-doc.cn

{
  "_embedded" : {
    "orders" : []
  }
}

4.1.2. Configuring link rendering

In HAL, the _links entry is a JSON object. The property names are link relations and each value is either a link object or an array of link objects.spring-doc.cn

For a given link relation that has two or more links, the spec is clear on representation:spring-doc.cn

Example 24. HAL document with two links associated with one relation
{
  "_links": {
    "item": [
      { "href": "https://myhost/cart/42" },
      { "href": "https://myhost/inventory/12" }
    ]
  },
  "customer": "Dave Matthews"
}

But if there is only one link for a given relation, the spec is ambiguous. You could render that as either a single object or as a single-item array.spring-doc.cn

By default, Spring HATEOAS uses the most terse approach and renders a single-link relation like this:spring-doc.cn

Example 25. HAL document with single link rendered as an object
{
  "_links": {
    "item": { "href": "https://myhost/inventory/12" }
  },
  "customer": "Dave Matthews"
}

Some users prefer to not switch between arrays and objects when consuming HAL. They would prefer this type of rendering:spring-doc.cn

Example 26. HAL with single link rendered as an array
{
  "_links": {
    "item": [{ "href": "https://myhost/inventory/12" }]
  },
  "customer": "Dave Matthews"
}

If you wish to customize this policy, all you have to do is inject a HalConfiguration bean into your application configuration. There are multiple choices.spring-doc.cn

Example 27. Global HAL single-link rendering policy
@Bean
public HalConfiguration globalPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); (1)
}
1 Override Spring HATEOAS’s default by rendering ALL single-link relations as arrays.

If you prefer to only override some particular link relations, you can create a HalConfiguration bean like this:spring-doc.cn

Example 28. Link relation-based HAL single-link rendering policy
@Bean
public HalConfiguration linkRelationBasedPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinksFor( //
          IanaLinkRelations.ITEM, RenderSingleLinks.AS_ARRAY) (1)
      .withRenderSingleLinksFor( //
          LinkRelation.of("prev"), RenderSingleLinks.AS_SINGLE); (2)
}
1 Always render item link relations as an array.
2 Render prev link relations as an object when there is only one link.

If neither of these match your needs, you can use an Ant-style path pattern:spring-doc.cn

Example 29. Pattern-based HAL single-link rendering policy
@Bean
public HalConfiguration patternBasedPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinksFor( //
          "http*", RenderSingleLinks.AS_ARRAY); (1)
}
1 Render all link relations that start with http as an array.
The pattern-based approach uses Spring’s AntPathMatcher.

All of these HalConfiguration withers can be combined to form one comprehensive policy. Be sure to test your API extensively to avoid surprises.spring-doc.cn

4.1.3. Link title internationalization

HAL defines a title attribute for its link objects. These titles can be populated by using Spring’s resource bundle abstraction and a resource bundle named rest-messages so that clients can use them in their UIs directly. This bundle will be set up automatically and is used during HAL link serialization.spring-doc.cn

To define a title for a link, use the key template _links.$relationName.title as follows:spring-doc.cn

Example 30. A sample rest-messages.properties
_links.cancel.title=Cancel order
_links.payment.title=Proceed to checkout

This will result in the following HAL representation:spring-doc.cn

Example 31. A sample HAL document with link titles defined
{
  "_links" : {
    "cancel" : {
      "href" : "…"
      "title" : "Cancel order"
    },
    "payment" : {
      "href" : "…"
      "title" : "Proceed to checkout"
    }
  }
}

4.1.4. Using the CurieProvider API

The Web Linking RFC describes registered and extension link relation types. Registered rels are well-known strings registered with the IANA registry of link relation types. Extension rel URIs can be used by applications that do not wish to register a relation type. Each one is a URI that uniquely identifies the relation type. The rel URI can be serialized as a compact URI or Curie. For example, a curie of ex:persons stands for the link relation type example.com/rels/persons if ex is defined as example.com/rels/{rel}. If curies are used, the base URI must be present in the response scope.spring-doc.cn

The rel values created by the default RelProvider are extension relation types and, as a result, must be URIs, which can cause a lot of overhead. The CurieProvider API takes care of that: It lets you define a base URI as a URI template and a prefix that stands for that base URI. If a CurieProvider is present, the RelProvider prepends all rel values with the curie prefix. Furthermore a curies link is automatically added to the HAL resource.spring-doc.cn

The following configuration defines a default curie provider:spring-doc.cn

@Configuration
@EnableWebMvc
@EnableHypermediaSupport(type= {HypermediaType.HAL})
public class Config {

  @Bean
  public CurieProvider curieProvider() {
    return new DefaultCurieProvider("ex", new UriTemplate("https://www.example.com/rels/{rel}"));
  }
}

Note that now the ex: prefix automatically appears before all rel values that are not registered with IANA, as in ex:orders. Clients can use the curies link to resolve a curie to its full form. The following example shows how to do so:spring-doc.cn

{
  "_links": {
    "self": {
      "href": "https://myhost/person/1"
    },
    "curies": {
      "name": "ex",
      "href": "https://example.com/rels/{rel}",
      "templated": true
    },
    "ex:orders": {
      "href": "https://myhost/person/1/orders"
    }
  },
  "firstname": "Dave",
  "lastname": "Matthews"
}

Since the purpose of the CurieProvider API is to allow for automatic curie creation, you can define only one CurieProvider bean per application scope.spring-doc.cn

4.2. HAL-FORMS

HAL-FORMS is designed to add runtime FORM support to the HAL media type.spring-doc.cn

HAL-FORMS "looks like HAL." However, it is important to keep in mind that HAL-FORMS is not the same as HAL — the two should not be thought of as interchangeable in any way.spring-doc.cn

— Mike Amundsen
HAL-FORMS spec

To enable this media type, put the following configuration in your code:spring-doc.cn

Example 32. HAL-FORMS enabled application
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class HalFormsApplication {

}

Anytime a client supplies an Accept header with application/prs.hal-forms+json, you can expect something like this:spring-doc.cn

Example 33. HAL-FORMS sample document
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "role" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees/1"
    }
  },
  "_templates" : {
    "default" : {
      "method" : "put",
      "properties" : [ {
        "name" : "firstName",
        "required" : true
      }, {
        "name" : "lastName",
        "required" : true
      }, {
        "name" : "role",
        "required" : true
      } ]
    },
    "partiallyUpdateEmployee" : {
      "method" : "patch",
      "properties" : [ {
        "name" : "firstName",
        "required" : false
      }, {
        "name" : "lastName",
        "required" : false
      }, {
        "name" : "role",
        "required" : false
      } ]
    }
  }
}

Check out the HAL-FORMS spec to understand the details of the _templates attribute. Read about the Affordances API to augment your controllers with this extra metadata.spring-doc.cn

As for single-item (EntityModel) and aggregate root collections (CollectionModel), Spring HATEOAS renders them identically to HAL documents.spring-doc.cn

4.2.1. Defining HAL-FORMS metadata

HAL-FORMS allows to describe criterias for each form field. Spring HATEOAS allows to customize those by shaping the model type for the input and output types and using annotations on them.spring-doc.cn

Each template will get the following attributes defined:spring-doc.cn

Table 1. Template attributes
Attribute Description

contentTypespring-doc.cn

The media type expected to be received by the server. Only included if the controller method pointed to exposes a @RequestMapping(consumes = "…") attribute, or the media type was defined explicitly when setting up the affordance.spring-doc.cn

methodspring-doc.cn

The HTTP method to use when submitting the template.spring-doc.cn

targetspring-doc.cn

The target URI to submit the form to. Will only be rendered if the affordance target is different than the link it was declared on.spring-doc.cn

titlespring-doc.cn

The human readable title when displaying the template.spring-doc.cn

propertiesspring-doc.cn

All properties to be submitted with the form (see below).spring-doc.cn

Each property will get the following attributes defined:spring-doc.cn

Table 2. Property attributes
Attribute Description

readOnlyspring-doc.cn

Set to true if there’s no setter method for the property. If that is present, use Jackson’s @JsonProperty(Access.READ_ONLY) on the accessors or field explicitly. Not rendered by default, thus defaulting to false.spring-doc.cn

regexspring-doc.cn

Can be customized by using JSR-303’s @Pattern annotation either on the field or a type. In case of the latter the pattern will be used for every property declared as that particular type. Not rendered by default.spring-doc.cn

requiredspring-doc.cn

Can be customized by using JSR-303’s @NotNull. Not rendered by default and thus defaulting to false. Templates using PATCH as method will automatically have all properties set to not required.spring-doc.cn

maxspring-doc.cn

The maximum value allowed for the property. Derived from Derived from JSR-303’s @Size, Hibernate Validator’s @Range or JSR-303’s @Max and @DecimalMax annotations.spring-doc.cn

maxLengthspring-doc.cn

The maximum length value allowed for the property. Derived from Hibernate Validator’s @Length annotation.spring-doc.cn

minspring-doc.cn

The minimum value allowed for the property. Derived from JSR-303’s @Size, Hibernate Validator’s @Range or JSR-303’s @Min and @DecimalMin annotations.spring-doc.cn

minLengthspring-doc.cn

The minimum length value allowed for the property. Derived from Hibernate Validator’s @Length annotation.spring-doc.cn

optionsspring-doc.cn

The options to select a value from when submitting the form. For details, see Defining HAL-FORMS options for a property.spring-doc.cn

promptspring-doc.cn

The user readable prompt to use when rendering the form input. For details, see Property prompts.spring-doc.cn

placeholderspring-doc.cn

A user readable placeholder, to give an example for a format expected. The way of defining those follows Property prompts but uses the suffix _placeholder.spring-doc.cn

typespring-doc.cn

The HTML input type derived from the explicit @InputType annotation, JSR-303 validation annotations or the property’s type.spring-doc.cn

For types that you cannot annotate manually, you can register a custom pattern via a HalFormsConfiguration bean present in the application context.spring-doc.cn

@Configuration
class CustomConfiguration {

  @Bean
  HalFormsConfiguration halFormsConfiguration() {

    HalFormsConfiguration configuration = new HalFormsConfiguration();
    configuration.registerPatternFor(CreditCardNumber.class, "[0-9]{16}");
  }
}

This setup will cause the HAL-FORMS template properties for representation model properties of type CreditCardNumber to declare a regex field with value [0-9]{16}.spring-doc.cn

Defining HAL-FORMS options for a property

For properties whose value is supposed to match a certain superset of values, HAL-FORMS defines the options sub-document within a property definition. Options available for a certain property can be described via HalFormsConfiguration's withOptions(…) taking a pointer to a type’s property and a creator function to turn a PropertyMetadata into a HalFormsOptions instance.spring-doc.cn

@Configuration
class CustomConfiguration {

  @Bean
  HalFormsConfiguration halFormsConfiguration() {

    HalFormsConfiguration configuration = new HalFormsConfiguration();
    configuration.withOptions(Order.class, "shippingMethod" metadata ->
      HalFormsOptions.inline("FedEx", "DHL"));
  }
}

See how we set up the option values FedEx and DHL as the options to select from for the Order.shippingMethod property. Alternatively, HalFormsOptions.remote(…) can point to a remote resource providing values dynamically. Fore more constraints on options settings, refer to the spec or the Javadoc of HalFormsOptions.spring-doc.cn

4.2.2. Internationalization of form attributes

HAL-FORMS contains attributes that are intended for human interpretation, like a template’s title or property prompts. These can be defined and internationalized using Spring’s resource bundle support and the rest-messages resource bundle configured by Spring HATEOAS by default.spring-doc.cn

Template titles

To define a template title use the following pattern: _templates.$affordanceName.title. Note that in HAL-FORMS, the name of a template is default if it is the only one. This means that you’ll usually have to qualify the key with the local or fully qualified input type name that affordance describes.spring-doc.cn

Example 34. Defining HAL-FORMS template titles
_templates.default.title=Some title (1)
_templates.putEmployee.title=Create employee (2)
Employee._templates.default.title=Create employee (3)
com.acme.Employee._templates.default.title=Create employee (4)
1 A global definition for the title using default as key.
2 A global definition for the title using the actual affordance name as key. Unless defined explicitly when creating the affordance, this defaults to the name of the method that has been pointed to when creating the affordance.
3 A locally defined title to be applied to all types named Employee.
4 A title definition using the fully-qualified type name.
Keys using the actual affordance name enjoy preference over the defaulted ones.
Property prompts

Property prompts can also be resolved via the rest-messages resource bundle automatically configured by Spring HATEOAS. The keys can be defined globally, locally or fully-qualified and need an ._prompt concatenated to the actual property key:spring-doc.cn

Example 35. Defining prompts for an email property
firstName._prompt=Firstname (1)
Employee.firstName._prompt=Firstname (2)
com.acme.Employee.firstName._prompt=Firstname (3)
1 All properties named firstName will get "Firstname" rendered, independent of the type they’re declared in.
2 The firstName property in types named Employee will be prompted "Firstname".
3 The firstName property of com.acme.Employee will get a prompt of "Firstname" assigned.

4.2.3. A complete example

Let’s have a look at some example code that combines all the definition and customization attributes described above. A RepresentationModel for a customer might look something like this:spring-doc.cn

class CustomerRepresentation
  extends RepresentationModel<CustomerRepresentation> {

  String name;
  LocalDate birthdate; (1)
  @Pattern(regex = "[0-9]{16}") String ccn; (2)
  @Email String email; (3)
}
1 We define a birthdate property of type LocalDate.
2 We expect ccn to adhere to a regular expression.
3 We define email to be an email using the JSR-303 @Email annotation.

Note that this type is not a domain type. It’s intentionally designed to capture a wide range of potentially invalid input so that potentially erroneous valies for the fields can be rejected at once.spring-doc.cn

Let’s continue by having a look at how a controller makes use of that model:spring-doc.cn

@Controller
class CustomerController {

  @PostMapping("/customers")
  EntityModel<?> createCustomer(@RequestBody CustomerRepresentation payload) { (1)
    // …
  }

  @GetMapping("/customers")
  CollectionModel<?> getCustomers() {

    CollectionModel<?> model = …;

    CustomerController controller = methodOn(CustomerController.class);

    model.add(linkTo(controller.getCustomers()).withSelfRel() (2)
      .andAfford(controller.createCustomer(null)));

    return ResponseEntity.ok(model);
  }
}
1 A controller method is declared to use the representation model defined above to bind the request body to if a POST is issued to /customers.
2 A GET request to /customers prepares a model, adds a self link to it and additionally declares an affordance on that very link pointing to the controller method mapped to POST. This will cause an affordance model to be built up, which — depending on the media type to be rendered eventually — will be translated into the media type specific format.

Next, let’s add some additional metadata to make the form more accessible to humans:spring-doc.cn

Additional properties declared in rest-messages.properties.
CustomerRepresentation._template.createCustomer.title=Create customer (1)
CustomerRepresentation.ccn._prompt=Credit card number (2)
CustomerRepresentation.ccn._placeholder=1234123412341234 (2)
1 We define an explicit title for the template created by pointing to the createCustomer(…) method.
2 We explicitly a prompt and placeholder for the ccn property of the CustomerRepresentation model.

If a client now issues a GET request to /customers using an Accept header of application/prs.hal-forms+json, the response HAL document is extended to a HAL-FORMS one to include the following _templates definition:spring-doc.cn

{
  …,
  "_templates" : {
    "default" : { (1)
      "title" : "Create customer", (2)
      "method" : "post", (3)
      "properties" : [ {
        "name" : "name",
        "required" : true,
        "type" : "text" (4)
      } , {
        "name" : "birthdate",
        "required" : true,
        "type" : "date" (4)
      } , {
        "name" : "ccn",
        "prompt" : "Credit card number", (5)
        "placeholder" : "1234123412341234" (5)
        "required" : true,
        "regex" : "[0-9]{16}", (6)
        "type" : "text"
      } , {
        "name" : "email",
        "prompt" : "Email",
        "required" : true,
        "type" : "email" (7)
      } ]
    }
  }
}
1 A template named default is exposed. Its name is default as it’s the sole template defined and the spec requires that name to be used. If multiple templates are attached (by declaring additional affordances) they will be each named after the method they’re pointing to.
2 The template title is derived from the value defined in the resource bundle. Note, that depending on the Accept-Language header sent with the request and the availability different values might returned.
3 The method attribute’s value is derived from the mapping of the method the affordance was derived from.
4 The type attribute’s value text is derived from the property’s type String. The same applies to birthdate property, but resulting in date.
5 The prompt and placeholder for the ccn property are derived from the resource bundle as well.
6 The @Pattern declaration for the ccn property is exposed as regex attribute of the template property.
7 The @Email annotation on the email property has been translated into the corresponding type value.

HAL-FORMS templates are considered by e.g. the HAL Explorer, which automatically renders HTML forms from those descriptions.spring-doc.cn

4.3. HTTP Problem Details

Problem Details for HTTP APIs is a media type to carry machine-readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.spring-doc.cn

HTTP Problem Details defines a set of JSON properties that carry additional information to describe error details to HTTP clients. Find more details about those properties in particular in the relevant section of the RFC document.spring-doc.cn

You can create such a JSON response by using the Problem media type domain type in your Spring MVC Controller:spring-doc.cn

Reporting problem details using Spring HATEOAS' Problem type
@RestController
class PaymentController {

  @PutMapping
  ResponseEntity<?> issuePayment(@RequestBody PaymentRequest request) {

    PaymentResult result = payments.issuePayment(request.orderId, request.amount);

    if (result.isSuccess()) {
      return ResponseEntity.ok(result);
    }

    String title = messages.getMessage("payment.out-of-credit");
    String detail = messages.getMessage("payment.out-of-credit.details", //
        new Object[] { result.getBalance(), result.getCost() });

    Problem problem = Problem.create() (1)
        .withType(OUT_OF_CREDIT_URI) //
        .withTitle(title) (2)
        .withDetail(detail) //
        .withInstance(PAYMENT_ERROR_INSTANCE.expand(result.getPaymentId())) //
        .withProperties(map -> { (3)
          map.put("balance", result.getBalance());
          map.put("accounts", Arrays.asList( //
              ACCOUNTS.expand(result.getSourceAccountId()), //
              ACCOUNTS.expand(result.getTargetAccountId()) //
          ));
        });

    return ResponseEntity.status(HttpStatus.FORBIDDEN) //
        .body(problem);
  }
}
1 You start by creating an instance of Problem using the factory methods exposed.
2 You can define the values for the default properties defined by the media type, e.g. the type URI, the title and details using internationalization features of Spring (see above).
3 Custom properties can be added via a Map or an explicit object (see below).

To use a dedicated object for custom properties, declare a type, create and populate an instance of it and hand this into the Problem instance either via ….withProperties(…) or on instance creation via Problem.create(…).spring-doc.cn

Using a dedicated type to capture extended problem properties
class AccountDetails {
  int balance;
  List<URI> accounts;
}

problem.withProperties(result.getDetails());

// or

Problem.create(result.getDetails());

This will result in a response looking like this:spring-doc.cn

A sample HTTP Problem Details response
{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30,
  "accounts": ["/account/12345",
               "/account/67890"]
}

4.4. Collection+JSON

Collection+JSON is a JSON spec registered with IANA-approved media type application/vnd.collection+json.spring-doc.cn

Collection+JSON is a JSON-based read/write hypermedia-type designed to support management and querying of simple collections.spring-doc.cn

— Mike Amundsen
Collection+JSON spec

Collection+JSON provides a uniform way to represent both single item resources as well as collections. To enable this media type, put the following configuration in your code:spring-doc.cn

Example 36. Collection+JSON enabled application
@Configuration
@EnableHypermediaSupport(type = HypermediaType.COLLECTION_JSON)
public class CollectionJsonApplication {

}

This configuration will make your application respond to requests that have an Accept header of application/vnd.collection+json as shown below.spring-doc.cn

The following example from the spec shows a single item:spring-doc.cn

Example 37. Collection+JSON single item example
{
  "collection": {
    "version": "1.0",
    "href": "https://example.org/friends/", (1)
    "links": [   (2)
      {
        "rel": "feed",
        "href": "https://example.org/friends/rss"
      },
      {
        "rel": "queries",
        "href": "https://example.org/friends/?queries"
      },
      {
        "rel": "template",
        "href": "https://example.org/friends/?template"
      }
    ],
    "items": [  (3)
      {
        "href": "https://example.org/friends/jdoe",
        "data": [  (4)
          {
            "name": "fullname",
            "value": "J. Doe",
            "prompt": "Full Name"
          },
          {
            "name": "email",
            "value": "[email protected]",
            "prompt": "Email"
          }
        ],
        "links": [ (5)
          {
            "rel": "blog",
            "href": "https://examples.org/blogs/jdoe",
            "prompt": "Blog"
          },
          {
            "rel": "avatar",
            "href": "https://examples.org/images/jdoe",
            "prompt": "Avatar",
            "render": "image"
          }
        ]
      }
    ]
  }
}
1 The self link is stored in the document’s href attribute.
2 The document’s top links section contains collection-level links (minus the self link).
3 The items section contains a collection of data. Since this is a single-item document, it only has one entry.
4 The data section contains actual content. It’s made up of properties.
5 The item’s individual links.

The previous fragment was lifted from the spec. When Spring HATEOAS renders an EntityModel, it will:spring-doc.cn

  • Put the self link into both the document’s href attribute and the item-level href attribute.spring-doc.cn

  • Put the rest of the model’s links into both the top-level links as well as the item-level links.spring-doc.cn

  • Extract the properties from the EntityModel and turn them into …​spring-doc.cn

When rendering a collection of resources, the document is almost the same, except there will be multiple entries inside the items JSON array, one for each entry.spring-doc.cn

Spring HATEOAS more specifically will:spring-doc.cn

  • Put the entire collection’s self link into the top-level href attribute.spring-doc.cn

  • The CollectionModel links (minus self) will be put into the top-level links.spring-doc.cn

  • Each item-level href will contain the corresponding self link for each entry from the CollectionModel.content collection.spring-doc.cn

  • Each item-level links will contain all other links for each entry from CollectionModel.content.spring-doc.cn

4.5. UBER - Uniform Basis for Exchanging Representations

UBER is an experimental JSON specspring-doc.cn

The UBER document format is a minimal read/write hypermedia type designed to support simple state transfers and ad-hoc hypermedia-based transitions.spring-doc.cn

— Mike Amundsen
UBER spec

UBER provides a uniform way to represent both single item resources as well as collections. To enable this media type, put the following configuration in your code:spring-doc.cn

Example 38. UBER+JSON enabled application
@Configuration
@EnableHypermediaSupport(type = HypermediaType.UBER)
public class UberApplication {

}

This configuration will make your application respond to requests using the Accept header application/vnd.amundsen-uber+json as show below:spring-doc.cn

Example 39. UBER sample document
{
  "uber" : {
    "version" : "1.0",
    "data" : [ {
      "rel" : [ "self" ],
      "url" : "/employees/1"
    }, {
      "name" : "employee",
      "data" : [ {
        "name" : "role",
        "value" : "ring bearer"
      }, {
        "name" : "name",
        "value" : "Frodo"
      } ]
    } ]
  }
}

This media type is still under development as is the spec itself. Feel free to open a ticket if you run into issues using it.spring-doc.cn

UBER media type is not associated in any way with Uber Technologies Inc., the ride sharing company.

4.6. ALPS - Application-Level Profile Semantics

ALPS is a media type for providing profile-based metadata about another resource.spring-doc.cn

An ALPS document can be used as a profile to explain the application semantics of a document with an application- agnostic media type (such as HTML, HAL, Collection+JSON, Siren, etc.). This increases the reusability of profile documents across media types.spring-doc.cn

— Mike Amundsen
ALPS spec

ALPS requires no special activation. Instead you "build" an Alps record and return it from either a Spring MVC or a Spring WebFlux web method as shown below:spring-doc.cn

Example 40. Building an Alps record
@GetMapping(value = "/profile", produces = ALPS_JSON_VALUE)
Alps profile() {

  return Alps.alps() //
      .doc(doc() //
          .href("https://example.org/samples/full/doc.html") //
          .value("value goes here") //
          .format(Format.TEXT) //
          .build()) //
      .descriptor(getExposedProperties(Employee.class).stream() //
          .map(property -> Descriptor.builder() //
              .id("class field [" + property.getName() + "]") //
              .name(property.getName()) //
              .type(Type.SEMANTIC) //
              .ext(Ext.builder() //
                  .id("ext [" + property.getName() + "]") //
                  .href("https://example.org/samples/ext/" + property.getName()) //
                  .value("value goes here") //
                  .build()) //
              .rt("rt for [" + property.getName() + "]") //
              .descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) //
              .build()) //
          .collect(Collectors.toList()))
      .build();
}
  • This example leverages PropertyUtils.getExposedProperties() to extract metadata about the domain object’s attributes.spring-doc.cn

This fragment has test data plugged in. It yields JSON like this:spring-doc.cn

Example 41. ALPS JSON
{
  "version": "1.0",
  "doc": {
    "format": "TEXT",
    "href": "https://example.org/samples/full/doc.html",
    "value": "value goes here"
  },
  "descriptor": [
    {
      "id": "class field [name]",
      "name": "name",
      "type": "SEMANTIC",
      "descriptor": [
        {
          "id": "embedded"
        }
      ],
      "ext": {
        "id": "ext [name]",
        "href": "https://example.org/samples/ext/name",
        "value": "value goes here"
      },
      "rt": "rt for [name]"
    },
    {
      "id": "class field [role]",
      "name": "role",
      "type": "SEMANTIC",
      "descriptor": [
        {
          "id": "embedded"
        }
      ],
      "ext": {
        "id": "ext [role]",
        "href": "https://example.org/samples/ext/role",
        "value": "value goes here"
      },
      "rt": "rt for [role]"
    }
  ]
}

Instead of linking each field "automatically" to a domain object’s fields, you can write them by hand if you like. It’s also possible to use Spring Framework’s message bundles and the MessageSource interface. This gives you the ability to delegate these values to locale-specific message bundles and even internationalize the metadata.spring-doc.cn

4.7. Community-based media types

Thanks to the ability to create your own media type, there are several community-led efforts to build additional media types.spring-doc.cn

4.7.1. JSON:API

Maven coordinates
<dependency>
    <groupId>com.toedter</groupId>
    <artifactId>spring-hateoas-jsonapi</artifactId>
    <version>{see project page for current version}</version>
</dependency>
Gradle coordinates
implementation 'com.toedter:spring-hateoas-jsonapi:{see project page for current version}'

Visit the project page for more details if you want snapshot releases.spring-doc.cn

4.7.2. Siren

Maven coordinates
<dependency>
    <groupId>de.ingogriebsch.hateoas</groupId>
    <artifactId>spring-hateoas-siren</artifactId>
    <version>{see project page for current version}</version>
    <scope>compile</scope>
</dependency>
Gradle coordinates
implementation 'de.ingogriebsch.hateoas:spring-hateoas-siren:{see project page for current version}'

4.8. Registering a custom media type

Spring HATEOAS allows you to integrate custom media types through an SPI. The building blocks of such an implementation are:spring-doc.cn

  1. Some form of Jackson ObjectMapper customization. In its most simple case that’s a Jackson Module implementation.spring-doc.cn

  2. A LinkDiscoverer implementation so that the client-side support is able to detect links in representations.spring-doc.cn

  3. A small bit of infrastructure configuration that will allow Spring HATEOAS to find the custom implementation and pick it up.spring-doc.cn

4.8.1. Custom media type configuration

Custom media type implementations are picked up by Spring HATEOAS by scanning the application context for any implementations of the HypermediaMappingInformation interface. Each media type must implement this interface in order to:spring-doc.cn

To define your own media type could look as simple as this:spring-doc.cn

@Configuration
public class MyMediaTypeConfiguration implements HypermediaMappingInformation {

  @Override
  public List<MediaType> getMediaTypes() {
    return Collections.singletonList(MediaType.parseMediaType("application/vnd-acme-media-type")); (1)
  }

  @Override
  public Module getJacksonModule() {
    return new Jackson2MyMediaTypeModule(); (2)
  }

  @Bean
  MyLinkDiscoverer myLinkDiscoverer() {
    return new MyLinkDiscoverer(); (3)
  }
}
1 The configuration class returns the media type it supports. This applies to both server-side and client-side scenarios.
2 It overrides getJacksonModule() to provide custom serializers to create the media type specific representations.
3 It also declares a custom LinkDiscoverer implementation for further client-side support.

The Jackson module usually declares Serializer and Deserializer implementations for the representation model types RepresentationModel, EntityModel, CollectionModel and PagedModel. In case you need further customization of the Jackson ObjectMapper (like a custom HandlerInstantiator), you can alternatively override configureObjectMapper(…).spring-doc.cn

Prior versions of reference documentation has mentioned implementing the MediaTypeConfigurationProvider interface and registering it with spring.factories. This is NOT necessary. This SPI is ONLY used for out-of-the-box media types provided by Spring HATEOAS. Merely implementing the HypermediaMappingInformation interface and registering it as a Spring bean is all that’s needed.spring-doc.cn

4.8.2. Recommendations

The preferred way to implement media type representations is by providing a type hierarchy that matches the expected format and can be serialized by Jackson as is. In the Serializer and Deserializer implementations registered for RepresentationModel, convert the instances into the media type-specific model types and then lookup the Jackson serializer for those.spring-doc.cn

The media types supported by default use the same configuration mechanism as third-party implementations would do. So it’s worth studying the implementations in the mediatype package. Note, that the built in media type implementations keep their configuration classes package private, as they’re activated via @EnableHypermediaSupport. Custom implementations should probably make those public instead to make sure, users can import those configuration classes from their application packages.spring-doc.cn