Spring HATEOAS
repository·main·Indexed 22 days ago
https://github.com/spring-projects/spring-hateoasA library for Spring MVC applications designed to simplify the creation of RESTful representations following the HATEOAS (Hypermedia as the Engine of Application State) principle. It provides APIs for automating link creation, representation assembly, and hypermedia-driven navigation using tools like Traverson, LinkDiscoverer, and RepresentationModel subtypes such as EntityModel and CollectionModel.
What's inside Spring HATEOAS
- Spring HATEOAS provides APIs to simplify the creation of REST representations that adhere to the HATEOAS (Hypermedia as the Engine of Application State) principle. It is specifically designed to work with Spring, particularly Spring MVC, by addressing the complexities of link creation and the assembly of hypermedia-driven representations.
Understand the new package structure in Spring HATEOAS 1.0
mainThe 1.0 release introduced a clear separation of APIs to support a hypermedia type registration API. The package structure is organized as follows:
- Client APIs: Located in the
clientpackage (e.g.,LinkDiscovererhas moved here). - Server APIs: Located in the
serverpackage (e.g.,LinkBuilderandEntityLinkshave moved here). - MVC Server APIs:
ControllerLinkBuilderhas moved toserver.mvcand is deprecated in favor ofWebMvcLinkBuilder. - Media Type Implementations: Located in the
mediatypepackage.
- Client APIs: Located in the
Use Links to indicate resource navigation
mainSpring HATEOAS uses the immutable
Linkvalue type to enrich resource representations with hypermedia. A link consists of a hypertext reference (the URI) and a link relation (the semantics of the relationship). By default, if no relation is provided, the link relation is set to the IANAselfrelation.You can use 'wither' methods on a
Linkinstance to set additional attributes as defined in RFC-8288.// Basic link creation Link link = Link.of("/some-resource"); // Link with a specific relation Link linkWithRel = Link.of("/some-resource", "next");Work with URI templates for parameterized links
mainA
Linkcan contain a URI template (RFC-6570) instead of a static URI. This allows clients to expand template variables without knowing the final URI structure.Key capabilities:
- The
Linkinstance indicates it is templated. - It exposes the parameters contained in the template.
- It allows expansion of those parameters into a concrete URI.
// Manually constructing a URI template with a request parameter UriTemplate template = UriTemplate.of("/{segment}/something") .with(new TemplateVariable("parameter", VariableType.REQUEST_PARAM)); assertThat(template.toString()).isEqualTo("/{segment}/something{?parameter}");- The
Attach affordances to links
mainAffordances provide metadata about how to use a resource (e.g., which HTTP methods are available). You can attach affordances to a link using
.andAffordance(afford(...)).Automatic Affordance Registration
You can point to controller methods to automatically capture metadata about request bodies and response types:
// Example: Associating a PUT and PATCH operation to a GET self link link.andAffordance(afford(employeeController::updateEmployee)) .andAffordance(afford(employeeController::partiallyUpdateEmployee));Manual Affordance Registration
If you need to build affordances manually, use the
AffordancesAPI. This is useful for defining custom payloads and query parameters:// Start from a Link instance Affordances affordances = Affordances.of(link); affordances .withHttpMethod(HttpMethod.PUT) .withPayloadDescription(Employee.class) .withName("update") .withQueryParameter("QueryParameter");Registering custom media types
mainTo register a custom media type, do not implement
MediaTypeConfigurationProvideror register it withspring.factories(this is reserved for Spring HATEOAS's out-of-the-box types).Instead, simply implement the
HypermediaMappingInformationinterface and register your implementation as a standard Spring bean.Build links in Spring MVC with WebMvcLinkBuilder
mainTo avoid brittle URI string concatenation, use
WebMvcLinkBuilderto create links by pointing to controller classes. This approach uses Spring'sServletUriComponentsBuilderto automatically resolve the base URI (protocol, host, port, etc.) from the current request.Basic Link Creation
You can create a link to a collection resource by referencing the controller class and specifying a relation:
import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*; Link link = linkTo(PersonController.class).withRel("people");Nested and Self Links
You can build nested paths using
.slash()and create self-referencing links using.withSelfRel():Person person = new Person(1L, "Dave", "Matthews"); Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();Creating URIs for Headers
WebMvcLinkBuildercan also produceURIinstances, which is useful for settingLocationheaders in responses:HttpHeaders headers = new HttpHeaders(); headers.setLocation(linkTo(PersonController.class).slash(person).toUri()); return new ResponseEntity<PersonModel>(headers, HttpStatus.CREATED);import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*; Link link = linkTo(PersonController.class).withRel("people");Get started with Spring HATEOAS
mainTo begin using Spring HATEOAS, you can follow the official Spring Getting Started guide which provides a practical walkthrough for implementing HATEOAS in a RESTful application.
Reference documentation and guides:
Enable HAL-FORMS media type
mainTo enable HAL-FORMS support (which adds runtime FORM support to HAL), you must include the appropriate configuration in your application. When a client requests
application/prs.hal-forms+json, the server will respond with HAL-FORMS documents containing_templatesdescribing available operations.// Example of an application with HAL-FORMS enabled @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) public class HalFormsApplication { // ... }Internationalize HAL link titles
mainHAL supports a
titleattribute for link objects. Spring HATEOAS automatically populates these using Spring's resource bundle abstraction.To define titles, create a resource bundle named
rest-messagesand use the key template_links.$relationName.title.# rest-messages.properties _links.cancel.title=Cancel order _links.payment.title=Proceed to checkoutActivate hypermedia support using `@EnableHypermediaSupport`
mainTo ensure that
RepresentationModelsubtypes (likeEntityModelandCollectionModel) are rendered according to specific hypermedia formats (such as HAL), use the@EnableHypermediaSupportannotation.When you apply this annotation, Spring HATEOAS performs the following automatic configurations:
- Jackson Integration: Registers the necessary Jackson modules to render models in the chosen hypermedia format.
- Link Discovery: If
JSONPathis on the classpath, it registers aLinkDiscovererto look up links byrelin plain JSON. - Entity Links: Enables entity links by default and bundles all
EntityLinksimplementations into aDelegatingEntityLinksinstance available for autowiring. - Relation Providers: Bundles all
RelProviderimplementations into aDelegatingRelProviderfor autowiring. This includes support for@Relationon domain types and Spring MVC controllers. IfEVO inflectoris on the classpath, it uses that library to pluralize collectionrelvalues.
@EnableHypermediaSupport(type = HypermediaType.HAL) class MyHypermediaConfiguration { // Configuration details }Configure WebTestClient for hypermedia testing
mainWhen testing hypermedia-enabled APIs with
WebTestClient, you must apply hypermedia support to the client instance. Note thatWebTestClientis immutable, so you must capture the returned mutated instance.Spring Boot Integration
In a
@SpringBootTestwith@AutoConfigureWebTestClient, autowire both theWebTestClient.Builderand theHypermediaWebTestClientConfigurer. Use the configurer to mutate the builder before calling.build().@SpringBootTest @AutoConfigureWebTestClient class WebClientBasedTests { @Test void exampleTest(@Autowired WebTestClient.Builder builder, @Autowired HypermediaWebTestClientConfigurer configurer) { client = builder.apply(configurer).build(); client.get().uri("/") .exchange() .expectBody(new TypeReferences.EntityModelType<Employee>() {}) .consumeWith(result -> { // assert against this EntityModel<Employee>! }); } }