Spring Boot HATEOAS Links Example

In this Spring Boot HATEOAS example, we will learn to add HATEOAS (Hypertext as the Engine of Application State) links to resource representations returned from REST APIs in a Spring boot application.

1. What is HATEOAS?

HATEOAS is one of the constraints of the REST architectural style first presented by Roy Fielding in his dissertation. The term hypermedia refers to any content that contains links to other forms of media such as other APIs, images, movies, and text. In HATEOAS, we insert the hypermedia links in the API response contents.

These hypermedia links allow the client to dynamically navigate to the appropriate resources by traversing the links.

2. Maven

In a Spring boot project, we need to add the spring-boot-starter-hateoas module.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

If not using Spring Boot, add the spring-hateoas dependency.

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>2.1.0</version>
</dependency>

3. Building Links with HATEOAS Module

Let’s discuss how to use the API to create and insert links in the REST responses.

3.1. API Support

The HATEOAS module provides given three classes to add the links to the resource representations.

  • RepresentationModel – Base class for DTOs to collect links.
  • Link – Immutable value object for links. It stores both a hypertext reference and a link relation. It exposes other attributes as defined in RFC-8288.
  • WebMvcLinkBuilder – Builder to ease building Link instances pointing to Spring MVC controllers.

To use the RepresentationModel, we extend the model class (such as Employee or Item) with RepresentationModel, create instances of the model class, populate the properties and add the desired links to it.

public class Employee extends RepresentationModel<Employee> {

  //...
}

3.2. Creating Link with Constructors

As stated earlier, the hateoas module works with an immutable Link type. Link.of() is an overloaded method that takes various types of arguments to create an immutable instance of Link.

Link of(String href)
Link of(String href, LinkRelation relation)
Link of(String href, String relation)
Link of(UriTemplate template, LinkRelation relation)
Link of(UriTemplate template, String relation)

Let us see an example to see how Link instances are created.

Link link = Link.of("/employee-report");

assertThat(link.getHref()).isEqualTo("/employee-report");
assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF);

link = Link.of("/employee-report", IanaLinkRelations.RELATED);

assertThat(link.getHref()).isEqualTo("/employee-report");
assertThat(link.getRel()).isEqualTo(LinkRelation.of(IanaLinkRelations.RELATED));

Please note that the href value can be a URI template as well and we can replace the placeholder values in runtime.

Link link = Link.of("/{department}/users/{?id}");

Map<String, Object> values = new HashMap<>();
values.put("department", "HR");
values.put("id", 123);

assertThat(link.expand(values).getHref()).isEqualTo("/HR/users?id=123");

The IanaLinkRelations class contains all the relation attribute values as per IANA (Internet Assigned Numbers Authority). The default relation value is IanaLinkRelations.SELF.

3.3. Creating Links with WebMvcLinkBuilder

Spring HATEOAS also provides a WebMvcLinkBuilder that lets us create links by pointing to controller classes.

For example, our controller class is:

@RestController
public class EmployeeController {

    @GetMapping("/employees")
    public EmployeeList getAllEmployees() {
    }

    @GetMapping("/employees/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") int id) {
    }

    @GetMapping("/employees/{id}/report")
    public ResponseEntity<EmployeeReport> getReportByEmployeeById(@PathVariable("id") int id) {
    }
}

We can create various links to the controller and its methods using WebMvcLinkBuilder.slash() method.

/* Link to "/employees" */
Link link = linkTo(EmployeeController.class).withRel("employees");

/* Link to "/employees/{id}" */
Employee e = new Employee(1, "Lokesh", "Gupta", "lokesh@gmail.com");
Link link = linkTo(EmployeeController.class).slash(e.getId()).withSelfRel();

We can also use create a Method instance and pass it to WebMvcLinkBuilder.methodOn().

Method method = EmployeeController.class.getMethod("getReportByEmployeeById", Integer.class);

Link link = linkTo(method, 111).withSelfRel();

//or

Link link = linkTo(methodOn(EmployeeController.class).getReportByEmployeeById(111)).withSelfRel();

3.4. Adding Link to RepresentationModel

The RepresentationModel provides the overloaded add() methods that can accept either a single Link or a list of Link instances.

Employee employee = ...; //Get an instance from database

// Self link
Link selfLink = linkTo(EmployeeController.class)
	.slash(employee.getId()).
	withSelfRel();

// Method link
Link reportLink = linkTo(methodOn(EmployeeController.class)
    .getReportByEmployeeById(employee.getId()))
    .withRel("report");

employee.add(selfLink);
employee.add(reportLink);

4. Demo

In this example, we have created three APIs with endpoints as below:

  • /employees
  • /employees/{id}
  • /employees/{id}/report

4.1. Resource Model

We created the three model classes Employee and EmployeeList for returning the resource representation for a single employee and employee collection. The EmployeeReportResult is returned when a report is executed for an employee.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "employee")
public class Employee extends RepresentationModel<Employee> {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  private String firstName;
  private String lastName;
  private String email;
}
@XmlRootElement(name = "employees")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeList extends RepresentationModel<EmployeeList> {

  private List<Employee> employees = new ArrayList<Employee>();
}
@XmlRootElement(name = "employee-report")
public class EmployeeReportResult extends RepresentationModel<EmployeeReportResult> {

  // ...
}

4.2. REST Controller Where We add the HATEOAS Links

Notice the addLinkToEmployee() method where we add the links to Employee object in the response.

@RestController
public class EmployeeController {

  @Autowired
  EmployeeRepository repository;

  @GetMapping("/employees")
  public EmployeeList getAllEmployees() {
    EmployeeList employeesList = new EmployeeList();

    for (Employee employee : repository.findAll()) {

      addLinkToEmployee(employee);
      employeesList.getEmployees().add(employee);
    }

    // Adding self link employee collection resource
    Link selfLink = linkTo(methodOn(EmployeeController.class).getAllEmployees()).withSelfRel();
    employeesList.add(selfLink);

    return employeesList;
  }

  @GetMapping("/employees/{id}")
  public ResponseEntity<Employee> getEmployeeById(
      @PathVariable("id") int id) {

    Optional<Employee> employeeOpt = repository.findById(id);

    if (employeeOpt.isEmpty()) {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    Employee employee = employeeOpt.get();
    addLinkToEmployee(employee);
    return new ResponseEntity<>(employee, HttpStatus.OK);
  }

  @GetMapping("/employees/{id}/report")
  public ResponseEntity<EmployeeReportResult> getReportByEmployeeById(
      @PathVariable("id") int id) {
    // Do some operation and return report
    return null;
  }

  private void addLinkToEmployee(Employee employee) {

    // Adding self link employee 'singular' resource
    Link link = linkTo(EmployeeController.class).slash(employee.getId()).withSelfRel();
    employee.add(link);

    // Adding method link employee 'singular' resource
    ResponseEntity<EmployeeReportResult> methodLinkBuilder =
        methodOn(EmployeeController.class).getReportByEmployeeById(employee.getId());
    Link reportLink = linkTo(methodLinkBuilder).withRel("employee-report");
    employee.add(reportLink);
  }
}

4.3. API Responses

Now check out the API responses to understand how the links are shown in the REST resource representations.

  • /employees
{
    "employees": [
        {
            "id": 1,
            "firstName": "Alex",
            "lastName": "Dave",
            "email": "alex@gmail.com",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/1"
                },
                "employee-report": {
                    "href": "http://localhost:8080/employees/1/report"
                }
            }
        },

        ...
        ...
    ],
    "_links": {
        "self": {
            "href": "http://localhost:8080/employees"
        }
    }
}
  • /employees/1
{
    "id": 1,
    "firstName": "Alex",
    "lastName": "Dave",
    "email": "alex@gmail.com",
    "_links": {
        "self": {
            "href": "http://localhost:8080/1"
        },
        "employee-report": {
            "href": "http://localhost:8080/employees/1/report"
        }
    }
}

5. Conclusion

As we saw in the demo adding HATEOAS links in a Spring Boot application is very much easy and requires very less time and effort. In return, it increases the discoverability and usefulness of APIs by many folds.

Happy Learning !!

Download Sourcecode

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode