Getting started with HATEOAS and Spring Boot

What is HATEOAS ? Hypermedia as the Engine of Application State is one of the constraints of the REST application architecture.

HATEOAS brings this concept of displaying related links for a given resource to RESTful services. When we return the details of a specific resource, we also return links to operations that can be performed on the resource, as well as links to related resources. If a service consumer can use the links from the response to perform transactions, then it would not need to hardcode all links.

Let’s see in practice how to do it. Build a new project using the “web” and “hateoas” starter:

spring init -dweb,hateoas demo-hateoas 

The following dependencies will be included:

<dependencies>         <dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-web</artifactId> 		</dependency> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-hateoas</artifactId> 		</dependency> 		<!-- Test --> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-test</artifactId> 			<scope>test</scope> 		</dependency> 	</dependencies> 

To keep it simple, we will create an In-Memory repository to query with hateoas as described in this tutorial: How to define a Mock Repository in Spring Boot

Let’s start from a the model:

package com.example.demohateoas.model;

public class Customer {
  private final Long id;
  private final String firstName;
  private final String lastName;

  public Customer(Long id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public Long getId() {
    return this.id;
  }

  public String getFirstName() {
    return this.firstName;
  }

  public String getLastName() {
    return this.lastName;
  }
}

The we will add a simple interface:

package com.example.demohateoas;

import java.util.List;

public interface CustomerRepository {
  List<Customer> findAll();

  Customer findCustomer(Long id);
}

Now we will define an implementation of this interface that is tagged with @Repository:

package com.example.demohateoas;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.util.ObjectUtils;

@Repository
public class MockCustomerRepository implements CustomerRepository {
  private final List<Customer> customers = new ArrayList<>();

  public MockCustomerRepository() {
    this.customers.add(new Customer(1L, "John", "Smith"));
    this.customers.add(new Customer(2L, "Mark", "Spencer"));
    this.customers.add(new Customer(2L, "Andy", "Doyle"));
  }

  @Override
  public List<Customer> findAll() {
    return this.customers;
  }

  @Override
  public Customer findOne(Long id) {
    for (Customer customer : this.customers) {
      if (ObjectUtils.nullSafeEquals(customer.getId(), id)) {
        return customer;
      }
    }
    return null;
  }
}

Here comes the most interesting part. We will add a Controller that leverages links in the response:

package com.example.demohateoas.controller;

import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demohateoas.model.Customer;
import com.example.demohateoas.model.CustomerRepository;

@Controller
@RequestMapping("/customers")
@ExposesResourceFor(Customer.class)
public class CustomerController {
  private final CustomerRepository repository;
  private final EntityLinks entityLinks;

  public CustomerController(CustomerRepository repository, EntityLinks entityLinks) {
    this.repository = repository;
    this.entityLinks = entityLinks;
  }

  @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
  HttpEntity<Resources<Customer>> showCustomers() {
    Resources<Customer> resources = new Resources<>(this.repository.findAll());
    resources.add(this.entityLinks.linkToCollectionResource(Customer.class));
    return new ResponseEntity<>(resources, HttpStatus.OK);
  }

  @GetMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
  HttpEntity<Resource<Customer>> showCustomer(@PathVariable Long id) {
    Resource<Customer> resource = new Resource<>(this.repository.findOne(id));
    resource.add(this.entityLinks.linkToSingleResource(Customer.class, id));
    return new ResponseEntity<>(resource, HttpStatus.OK);
  }
}

As you can see, the key classes returned from the GET request are org.springframework.hateoas.Resource and org.springframework.hateoas.Resources which are simple Resources wrapping a domain object capable of adding links to it. If a single Resource has to be enriched with links the method linkToSingleResource is used. On the other hand, for Collections the linkToCollectionResource is used instead.

The Main applicaiton class completes the application:

package com.example.demohateoas;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

If you build and run the application, you will see that both in the Root Web context and in the /{id} Path a Link is provided, to enable navigation:

HATEOAS spring boot tutorial with exampleHATEOAS spring boot tutorial with example

Conclusion

In this tutorial we have learned how HATEOAS provides information to navigate the site’s REST interfaces dynamically by including hypermedia links with the responses. This capability, however, differs from that of SOA-based systems and WSDL-driven interfaces where servers and clients usually must access a fixed specification that might be staged somewhere else on the website, on another website.