Spring HATEOAS: Hypermedia-Driven RESTful Web Services

Introduction

REST APIs are flexible and allow developers to make decoupled systems. With the rise of the microservice architecture - REST has matured even more as microservices can be built irrespective of the language or the framework used in the application.

Being "in the spotlight" - this means that new types get derived or built around REST APIs, which brings us to HATEOAS.

What is HATEOAS?

Being in the spotlight, different architecture techniques focused around REST fundamentals are being introduced.

Hypermedia as the Engine of Application State (HATEOAS) is an architectural approach to enhance the usability of REST APIs for the applications consuming the APIs.

The main purpose of HATEOAS is to provide extra information in REST API responses so that the API users can get additional endpoint details from a single call. This allows users to build their systems with dynamic API calls, moving from one endpoint to another using the information retrieved from each call.

To understand this better, take a look at the following API response:

{
    "id": 1,
    "name": "Dr. Sanders",
    "speciality": "General",
    "patientList": [
        {
            "id": 1,
            "name": "J. Smalling",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/1"
                }
            }
        }
    ],
    "_links": {
        "self": {
            "href": "http://localhost:8080/doctors/1"
        },
        "patientList": {
            "href": "http://localhost:8080/doctors/1/patients"
        }
    }
}

Apart from getting the details about the doctor, the API response also provides extra information in the form of links. For example, a link to fetch all patients of a single doctor is attached too.

What we've got here is a resource-enriched response, where the provided links are resources that enrich our response with additional information.

Spring HATEOAS

Spring HATEOAS provides libraries to implement the HATEOAS architecture in a Spring application with ease. Using the Spring HATEOAS API, links can be created and returned as part of API response object.

Spring HATEOAS dependencies

Using Maven, adding Spring HATEOAS is as easy as including the dependencies:

<dependency>
    <groupId>org.springframework.plugin</groupId>
    <artifactId>spring-plugin-core</artifactId>
    <version>[2.0.0.RELEASE,)</version>
</dependency>
<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>[1.0.3.RELEASE,)</version>
</dependency>

Alternatively, using Gradle, you can add:

implementation 'org.springframework.plugin:spring-plugin-core:2.+'
implementation 'org.springframework.hateoas:spring-hateoas:1.+'

Spring Boot HATEOAS dependencies

Even easier, for Spring Boot applications, you can use the spring-boot-starter-hateoas Maven dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
    <version>[2.2.4.RELEASE,)</version>
</dependency>

Similarly, if you're using Gradle, you can simply add:

implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.+'

Using the spring-boot-starter-hateoas dependency includes spring-hateoas and spring-boot-starter-web dependencies, so naturally, no other starters are needed.

Spring HATEOAS Building Blocks

The basic building blocks for Spring HATEOAS are Links and RepresentationModels (a container for a collection of Links).

The RepresentationModel is then extended into EntityModel (for single resources) and CollectionModel (for multiple resources), as well as a PagedModel.

Let's take a brief moment to explain each of these before implementing them in a working demonstration.

The immutable Link object is used to store metadata for a resource (URI or location) - and the end-user can navigate to the resources that enrich our API response. A basic link with a resource URI could look like:

"_links": {
    "self": {
        "href": "http://localhost:8080/doctors/1"
    }
}

The link contains a href attribute, pointing to the URI of the resource. The href attribute is wrapped inside a self tag - which identifies the relationship with the entity. This means that the resource is pointing to itself, essentially.

Why have the resource point to itself?

The returned resources might not be the full representation of itself. A doctor can have a list of patients, but we might not want to return it by default.

If we'd then want to take a look at the doctor's list, we can navigate to it through the link.

Representation Models

The RepresentationModel acts as a root class for all other Spring HATEOAS model classes. It contains a collection of Links and provides method to add/remove them.

Creating your own model is as easy as extending the RepresentationModel class. Otherwise, you can use any of the readily available models:

  • Entity Model: The EntityModel is used to represent a resource that corresponds to a single object. You can wrap your resource with the EntityModel and pass it to a calling service or return it through a REST endpoint.

  • Collection Model: Similar to EntityModel, the CollectionModel is used to wrap resources - though, it wraps a resource that corresponds to a collection of objects.

  • Paged Model: Additionally, as many REST API endpoints return responses, which are pageable collections, Spring HATEOAS provides the PagedModel to represent such resources.

Let's create a sample resource which extends the RepresentationModel class:

public class Doctor extends RepresentationModel<Doctor> {
    private int id;
    private List<Patient> patientList;
}

For now our Doctor model only has an id property and a list of patients. Next, we'll be adding a Link to the resource, which will point the resource to itself.

Link Object

Spring HATEOAS Link objects take String arguments to specify the URI and the relationship between the entities. These are basically the href and rel attributes:

Link selfLink = new Link("http://localhost:8080/doctors/1", "self");

Doctor doctor = new Doctor();
doctor.add(selfLink);

When a doctor object is returned (as shown in the demo application in later sections), the response body will contain:

"_links": {
    "self": {
        "href": "http://localhost:8080/doctors/1"
    }
}
MVC LinkBuilder

Hardcoding values into the constructor of the Link class isn't advised though. It quickly gets difficult to manage and update them as your application/API grows. To combat this, we can use the WebMvcLinkBuilder, which allows us to create links using controller classes and pointing to their methods.

Let's recreate the link from the previous example using WebMvcLinkBuilder:

Link link = linkTo(methodOn(DoctorController.class).getDoctorById(id)).withSelfRel();

Here, we're using the more programmatic approach to creating links. It points to the getDoctorById() method inside the DoctorController class. Since it points to itself, we use the withSelfRel() method to specify the relationship.

Alternatively, we could've used the withRel() method and passed a String with a different relationship.

Spring HATEOAS will translate the endpoint details from the controller class and the method that we have provided to the WebMvcLinkBuilder. The output from this Link object will be exactly the same as the one generated in the preceding example.

To create links for resources which have a relationship between them or they point to a different resource, we'd use the withRel() method. Using this we can specify the endpoint with which the linked resource can be accessed:

Link link = linkTo(methodOn(DoctorController.class)
                .getDoctorPatients(doctor.getId()))
                .withRel("patientList");

The above code snippet specifies that the user can get the patientList for the doctor object, using the getDoctorPatients() method inside DoctorController class. When added to the response body, it generates the following link:

"_links": {
    "patientList": {
        "href": "http://localhost:8080/doctors/1/patients"
    }
}

Notice that we didn't provide any URL when creating the link. Spring HATEOAS is able to extract the information from the link builder and generate a URL based on the mappings we've used.

Configuration

To properly render different RepresentationModel subtypes, you can enable hyper media representation using the @EnableHypermediaSupport annotation. You can pass the HypermediaType as an argument to this annotation, allowing you to specify the hypermedia type, like JSON, UBER, HAL, etc. Using the annotation allows Spring to configure necessary Jackson modules to render hypermedia correctly.

Typically, Spring will detect the technology stack you're using and automatically adjust the configuration when you add the annotation. Though, if you have some custom requirements, we suggest you go through the official documentation.

Demo Application

With all that said, let's write a simple Spring application with HATEOAS support by going over to Spring Initializr and generating a blank Spring Boot application with the Spring HATEOAS (spring-boot-hateoas-starter) dependency:

spring initializr blank project

Creating a Resource

For any resource which is to be exposed through the REST API, it needs to extend RepresentationModel. By extending the RepresentationModel class, we also inherit the add() method, which is used to attach links to it.

Lets create a model for a Doctor:

public class Doctor extends RepresentationModel<Doctor> {
    private int id;
    private String name;
    private String speciality;
    private List<Patient> patientList;
}

As the Doctor class has a relationship with patients, let's create the Patient model as well:

public class Patient extends RepresentationModel<Patient> {
    private int id;
    private String name;
}

Next, in a controller, in our case a DoctorController, we'll autowire the DoctorService:

@RestController
@RequestMapping(value = "/doctors")
public class DoctorController {

    @Autowired
    DoctorService doctorService;
}

As you might expect, it contains methods such as getDoctor(), getDoctorWithPatients(), getDoctors(), etc, which all return a Doctor or a List<Doctor>. The implementation is omitted for brevity - if you'd like to take a look, the code is up on GitHub.

With this, we've created a resource. When retrieving resources, we'll either expect a single resource or a collection of resources. As stated earlier, we'll wrap them in an EntityModel or CollectionModel, respectively.

Retrieving a Single Resource

Let's first implement the functionality of fetching a single doctor. Since we expect the API call to return a single resource, we will wrap our response inside an EntityModel class:

@GetMapping(value = "/{id}")
public EntityModel<Doctor> getDoctorById(@PathVariable int id) {
    Doctor doctor = doctorService.getDoctorWithPatients(id);

    for (final Patient patient : doctor.getPatientList()) {
        Link selfLink = linkTo(methodOn(PatientController.class)
                               .getPatientById(patient.getId())).withSelfRel();
        patient.add(selfLink);
    }

    doctor.add(linkTo(methodOn(DoctorController.class)
                      .getDoctorById(id)).withSelfRel());
    doctor.add(linkTo(methodOn(DoctorController.class)
                      .getDoctorPatients(doctor.getId())).withRel("patientList"));

    return new EntityModel<>(doctor);
}

After retrieving the Doctor object, we are traversing over the list of associated patients and adding a link for each of them. Each of these links can be used to get each individual Patient through the PatientController.

Similarly, we are adding a self link to the Doctor which was used to make the API calls. Along with the self link we are also adding a relational link, pointing to the patient list.

At the end of the method we have wrapped our Doctor object in an EntityModel class and this EntityModel is returned as the response:

{
    "id": 1,
    "name": "Dr. Sanders",
    "speciality": "General",
    "patientList": [
        {
            "id": 1,
            "name": "J. Smalling",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/1"
                }
            }
        },
        {
            "id": 2,
            "name": "Samantha Williams",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/2"
                }
            }
        }
    ],
    "_links": {
        "self": {
            "href": "http://localhost:8080/doctors/1"
        },
        "patientList": {
            "href": "http://localhost:8080/doctors/1/patients"
        }
    }
}

"Dr. Sanders" has "J. Smalling" and "Samantha Williams" as their patients and both the endpoint for the doctor and the endpoint for a list of the doctor's patients are added to the response - making it resource-enriched.

Retrieving Multiple Resources

Let's create another GET call which returns all the available doctors in the system. Now that the response we are expecting will be a collection of Doctor objects, we will wrap the response inside the CollectionModel:

@GetMapping
public CollectionModel<Doctor> getDoctors() {
    List<Doctor> doctors = doctorService.getDoctorsWithPatients();

    for (final Doctor doctor : doctors) {
        doctor.add(linkTo(methodOn(DoctorController.class)
                          .getDoctorById(doctor.getId())).withSelfRel());
        doctor.add(linkTo(methodOn(DoctorController.class)
                          .getDoctorPatients(doctor.getId())).withRel("patientList"));

        for (final Patient patient : doctor.getPatientList()) {
            Link selfLink = linkTo(methodOn(PatientController.class)
                                   .getPatientById(patient.getId())).withSelfRel();
            patient.add(selfLink);
        }
    }

    Link link = linkTo(methodOn(DoctorController.class).getDoctors()).withSelfRel();

    return new CollectionModel<>(doctors, link);
}

In this method, along with the self link for the REST call itself, we are also adding a self link to retrieve each individual doctor. Each doctor has a relational link, which points to the associated patients. Inside the patient list, each patient also has a self link, which can be used to retrieve the specific patient as well.

Once all the links are added we have wrapped the collection of Doctor objects inside a CollectionModel and returned it:

{
    "_embedded": {
        "doctorList": [
            {
                "id": 1,
                "name": "Dr. Sanders",
                "speciality": "General",
                "patientList": [
                    {
                        "id": 1,
                        "name": "J. Smalling",
                        "_links": {
                            "self": {
                                "href": "http://localhost:8080/patients/1"
                            }
                        }
                    }
                ],
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/doctors/1"
                    },
                    "patientList": {
                        "href": "http://localhost:8080/doctors/1/patients"
                    }
                }
            },
            {
                "id": 2,
                "name": "Dr. Goldberg",
                "speciality": "General",
                "patientList": [
                    {
                        "id": 4,
                        "name": "K. Oliver",
                        "_links": {
                            "self": {
                                "href": "http://localhost:8080/patients/4"
                            }
                        }
                    }
                ],
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/doctors/2"
                    },
                    "patientList": {
                        "href": "http://localhost:8080/doctors/2/patients"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/doctors"
        }
    }
}

As you can see from the output, just by making a single call, the user can discover additional information, which wouldn't otherwise be present.

Conclusion

Spring HATEOAS provides the required libraries and infrastructure to implement the HATEOAS architecture in Spring-based applications.

As evident from the outputs, users can discover additional information from a single REST call. Using this information, it's easier to build dynamic REST clients.

In this article, we've discussed how HATEOAS works, Spring's implementation of it, and finished by building a simple application to demonstrate the concepts.

The source code for the sample code can be found here on GitHub.