If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

This article explains the process of creating hypermedia-driven REST web service using Spring HATEOAS project.

2. Spring-HATEOAS

The Spring HATEOAS project is a library of APIs that we can use to easily create REST representations that follow the principle of HATEOAS (Hypertext as the Engine of Application State).

Generally speaking, the principle implies that the API should guide the client through the through the application by returning relevant information about the next potential steps, along with each response.

In this article we are going to build an example using Spring HATEOAS with the goal of decoupling the client and server, and theoretically allowing the API to change its URI scheme without breaking clients.

3. Preparation

First, let’s add Spring HATEOAS dependency:

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

Next, we have the Customer resource without Spring HATEOAS support:

public class Customer {

    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

And we have a controller class without Spring HATEOAS support:

@RestController
@RequestMapping(value = "/customers")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @RequestMapping(value = "/{customerId}", method = RequestMethod.GET)
    public Customer getCustomerById(@PathVariable String customerId) {
        return customerService.getCustomerDetail(customerId);
    }
}

Finally, the customer resource representation:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company"
}

4. Adding HATEOAS Support

In a Spring HATEOAS project, we don’t need to either look up the Servlet context nor concatenate the path variable to the base URI. Spring HATEOAS offers three abstractions for creating the URI – ResourceSupport, Link and ControllerLinkBuilder. These are used to create the metadata and associate it to the resource representation.

4.1. Adding Hypermedia Support to a Resource

Spring HATEOAS project provides a base class called ResourceSupport to inherit from when creating resource representation.

public class Customer extends ResourceSupport {
    private String customerId;
    private String customerName;
    private String companyName;
 
    // standard getters and setters
}

The Customer resource extends from ResourceSupport class to inherit the add() method. So once we create a link, we can easily set that value to the resource representation without adding any new fields to it.

Spring HATEOAS provides a Link object to store the metadata (location or URI of the resource).

We’ll first create a simple link manually:

Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");

The Link object follows the Atom link syntax and consists of a rel which identifies relation to the resource and href attribute which is the actual link itself.

Here’s how the Customer resource looks now that it contains the new link:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company",
    "_links":{
        "self":{
            "href":"http://localhost:8080/spring-security-rest/api/customers/10A"
         }
    }
}

The URI associated with the response is qualified as a self link. The semantics of the self relation is clear – it’s simply the canonical location the Resource can be accessed at.

Another very important abstraction offered by the library is the ControllerLinkBuilder – which simplifies building URIs by avoiding hard-coded the links.

The following snippet shows building the customer self-link using the ControllerLinkBuilder class:

linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();

Let’s have a look:

  • the linkTo() method inspects the controller class and obtains its root mapping
  • the slash() method adds the customerId value as the path variable of the link
  • finally, the withSelfMethod() qualifies the relation as a self-link

5. Relations

In the previous section, we’ve shown a self-referencing relation. More complex systems may involve other relations as well.

For example, a customer can have a relationship to orders. The Order class will be modeled as a resource as well:

public class Order extends ResourceSupport {
    private String orderId;
    private double price;
    private int quantity;

    // standard getters and setters
}

At this point, the CustomerController controller can be extended with a method that returns all orders of a particular customer:

@RequestMapping(value = "/{customerId}/orders", method = RequestMethod.GET , 
  produces = {"application/hal+json"})
public Resources<Order> getOrdersForCustomer(@PathVariable final String customerId) {
    List<Order> orders = orderService.getAllOrdersForCustomer(customerId);
    for (final Order order : orders) {
        Link selfLink = linkTo(methodOn(CustomerController.class)
          .getOrderById(customerId, order.getOrderId())).withSelfRel();
        order.add(selfLink);
    }
 
    Link link = linkTo(methodOn(CustomerController.class)
      .getOrdersForCustomer(customerId)).withSelfRel();
    Resources<Order> result = new Resources<Order>(orders, link);
    return result;
}

Our method returns a Resources object to comply with the HAL return type, as well as a “_self” link for each of the orders and the full list.

An important thing to notice here is that the hyperlink for the customer orders depends on the mapping of getOrdersForCustomer() method. We’ll refer to this types of links as method links and show how the ControllerLinkBuilder can assist in their creation.

The ControllerLinkBuilder offers rich support for Spring MVC Controllers. The following example shows how to build HATEOAS hyperlinks based on the getOrdersForCustomer() method of the CustomerController class:

Link ordersLink = linkTo(methodOn(CustomerController.class)
  .getOrdersForCustomer(customerId)).withRel("allOrders");

The methodOn() obtains the method mapping by making dummy invocation of the target method on the proxy controller and sets the customerId as the path variable of the URI.

7. Spring HATEOAS in Action

Let’s put the self-link and method link creation all together in a getAllCustomers() method.

@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json" })
public Resources<Customer> getAllCustomers() {
    List<Customer> allCustomers = customerService.allCustomers();

    for (Customer customer : allCustomers) {
        String customerId = customer.getCustomerId();
        Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
        customer.add(selfLink);
        if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
            Link ordersLink = linkTo(methodOn(CustomerController.class)
              .getOrdersForCustomer(customerId)).withRel("allOrders");
            customer.add(ordersLink);
        }
    }

    Link link = linkTo(CustomerController.class).withSelfRel();
    Resources<Customer> result = new Resources<Customer>(allCustomers, link);
    return result;
}

Let’s invoke the getAllCustomers() method:

curl http://localhost:8080/spring-security-rest/api/customers

And examine the result:

{
  "_embedded": {
    "customerList": [{
        "customerId": "10A",
        "customerName": "Jane",
        "companyName": "ABC Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
          }
        }
      },{
        "customerId": "20B",
        "customerName": "Bob",
        "companyName": "XYZ Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B/orders"
          }
        }
      },{
        "customerId": "30C",
        "customerName": "Tim",
        "companyName": "CKV Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/30C"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers"
    }
  }
}

Within each resource representation, there is a self link and the allOrders link to extract all orders of a customer. If a customer does not have orders, then the link for orders will not appear.

This example demonstrates how Spring HATEOAS fosters API discoverability in a rest web service. If the link exists, the client can follow it and get all orders for a customer:

curl http://localhost:8080/spring-security-rest/api/customers/10A/orders
{
  "_embedded": {
    "orderList": [{
        "orderId": "001A",
        "price": 150,
        "quantity": 25,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/001A"
          }
        }
      },{
        "orderId": "002A",
        "price": 250,
        "quantity": 15,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/002A"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
    }
  }
}

8. Conclusion

In this tutorial, we have discussed how to build a hypermedia-driven Spring REST web service using Spring HATEOAS project.

In the example, we see that client can have a single entry point to the application and further actions can be taken based on the metadata in the response representation. This allows the server to change its URI scheme without breaking the client. Also, the application can advertise new capabilities by putting new links or URIs in the representation.

The full implementation of this article can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

newest oldest most voted
Notify of
Wei Ledoux
Guest
Wei Ledoux

Oh, my! Finally, I understand HATEOAS and know how to implement it. Thanks a lot!

Patrick Davenport
Guest
Patrick Davenport

How do you use Hateoas to create a new resource with sub resources? Like if you have an account that must also have a profile. Each is an independent resource, but they are transactionally required.

Grzegorz Piwowarek
Guest
Grzegorz Piwowarek

Actually, it does not have a lot to do with the HATEOAS itself – it’s more REST-related. In such case, you just need to contain all necessary info in one POST request. The resource would need to contain simply other nested resources. The actual separation would be done in one of next layers