Spring HATEOAS- a Hypermedia-Driven RESTful Web Service

In this Spring HATEOAS tutorial we will discuss about an another way of use of Restfull API. HATEOAS (Hypermedia as the Engine of Application State)- a Hypermedia-Driven RESTful Web Service. It is a constraint of the REST application architecture.

Spring HATEOAS

Table of Contents

  1. Introduction
  2. What is HATEOAS?
  3. What is need of HATEOAS?
  4. Features of HATEOAS
  5. Build a a Hypermedia-Driven RESTful Web Service
    1. Maven Dependency for Spring HATEOAS
    2. Implementation With LinkBuilder API
    3. ResourceSupport Implementation
  6. Summary


1. Introduction
Here we using Spring HATEOAS for creating very simple example of restful web service. Before that lets take understanding about HATEOAS (Hypermedia as the Engine of Application State) is a constraint of the REST application architecture.

2. What is HATEOAS?
HATEOAS is an another way to creating RESTful web services such that the client can dynamically discover the next or previous actions available to it at run-time from the server. These actions forward to the client as the Response body or Response Header in the for the links. HATEOAS is considered the final level of REST. This means that each link is presumed to implement the standard REST verbs of GET, POST, PUT, and DELETE.

3. What is need of HATEOAS?
Restful web service is very popular due to light weight and its six contraints. But there is drawback of Restful web service for large system because of each services would have a seperate URL to be defined. It is not easy to remember and maintain the complete list of services and state transition by the clients.

HATEOAS approach comes into picture with Restful API to overcome this problem. For All the client should require to get started is an initial URI, and set of standardized media types. Once it has loaded the initial URI, all future application state transitions will be driven by the client selecting from choices provided by the server. A hypermedia-driven site provides information to navigate the site’s REST interfaces dynamically by including hypermedia links with the responses.

4. Features

  • Model classes for link, resource representation models
  • Link builder API to create links pointing to Spring MVC controller methods
  • Support for hypermedia formats like HAL


5. Build a a Hypermedia-Driven RESTful Web Service
We will create a simple example of hypermedia-driven REST service with Spring HATEOAS.

5.1 Maven Dependency for Spring HATEOAS
If you are using Maven, please add these dependencies to your pom.xml file.

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

5.2 Implementation With LinkBuilder API
A Simple HATEOAS Application

A simple example of a Spring HATEOAS project is freely available on Github

https://github.com/DOJ-SoftwareConsultant/spring-hateoas-rest-ws

This simple example consists of 2 media types: ‘Account‘ and ‘AccountHolder

spring-hateoas-app

AccountController.java

/**
 * 
 */
package com.doj.hateoas.ws.accounts;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Dinesh.Rajput
 *
 */
@RestController
public class AccountController {

 protected Logger logger = Logger
   .getLogger(AccountController.class.getName());
 
 @Autowired
 AccountRepository accountRepository;
 
 @RequestMapping("/accounts")
 public List<Resource<Account>> all() {
  logger.info("accounts all() invoked");
  List<Account> accounts = accountRepository.getAllAccounts();
  List<Resource<Account>> resources = new ArrayList<Resource<Account>>();
  for (Account account : accounts) {
          resources.add(getAccountResource(account));
       }
  logger.info("accounts all() found: " + accounts.size());
  return resources;
 }
 
 @RequestMapping(value= "/accounts/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
 public Resource<Account> byId(@PathVariable("id") Long id) {
  logger.info("accounts byId() invoked: " + id);
  Account account = accountRepository.getAccount(id.toString());
  Resource<Account> resource = new Resource<Account>(account);
  resource.add(linkTo(methodOn(AccountController.class).all()).withRel("accounts"));
  resource.add(linkTo(methodOn(AccountController.class).findAccountHolderById(account.getAccountId(), account.getAccountHolder().getUserId())).withRel("accountHolder"));
  logger.info("accounts byId() found: " + account);
  return resource;
 }
 
 @RequestMapping(value= "/accounts/{id}/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
 public Resource<AccountHolder> findAccountHolderById(@PathVariable("id") Long id, @PathVariable("userId") Long userId) {
  logger.info("accounts findAccountHolderById() invoked: " + id);
  Account account = accountRepository.getAccount(id.toString());
  AccountHolder accountHolder = account.getAccountHolder();
  Resource<AccountHolder> resource = new Resource<AccountHolder>(accountHolder);
  resource.add(linkTo(methodOn(AccountController.class).byId(account.getAccountId())).withRel("account"));
  logger.info("accounts findAccountHolderById() found: " + account);
  return resource;
 }
 
 private Resource<Account> getAccountResource(Account account) {
  Resource<Account> resource = new Resource<Account>(account);
  resource.add(linkTo(methodOn(AccountController.class).byId(account.getAccountId())).withSelfRel());
  resource.add(linkTo(methodOn(AccountController.class).findAccountHolderById(account.getAccountId(), account.getAccountHolder().getUserId())).withRel("accountHolder"));
  return resource;
 }
 
}

Look at the first two import statements, those are static imports as part of the spring hateoas api. linkTo and methodOn are used for adding the link to the response.

The entry point to our API is :
http://localhost:1111/accounts

[
{
accountId: 1000,
accountHolder: {
userId: 5115,
name: "Arnav",
address: "Noida"
},
amount: 1039.13,
ifscCode: "AA992QA",
links: [
{
rel: "self",
href: "http://localhost:1111/accounts/1000"
},
{
rel: "accountHolder",
href: "http://localhost:1111/accounts/1000/5115"
}
]
},
{
accountId: 2000,
accountHolder: {
userId: 2089,
name: "Anamika",
address: "Noida"
},
amount: 1239.43,
ifscCode: "AB966QJ",
links: [
{
rel: "self",
href: "http://localhost:1111/accounts/2000"
},
{
rel: "accountHolder",
href: "http://localhost:1111/accounts/2000/2089"
}
]
},
{
accountId: 3000,
accountHolder: {
userId: 1286,
name: "Dinesh",
address: "Noida"
},
amount: 3339.61,
ifscCode: "AD912SA",
links: [
{
rel: "self",
href: "http://localhost:1111/accounts/3000"
},
{
rel: "accountHolder",
href: "http://localhost:1111/accounts/3000/1286"
}
]
}
]

This will basically list all the accounts available at our repository.
This response not only has the account’s detail, but includes the self-linking URL where that account is located.

  • rel means relationship. In this case, it’s a self-referencing hyperlink. More complex systems might include other relationships. For example, an account might have a “rel”:”accountHolder” relationship, linking the order to its customer.
  • href is a complete URL that uniquely defines the resource.

It provides links to any account detail of our api telling it the URLs it can use to:

http://localhost:1111/accounts/{id}

{
accountId: 1000,
accountHolder: {
userId: 5115,
name: "Arnav",
address: "Noida"
},
amount: 1039.13,
ifscCode: "AA992QA",
_links: {
accounts: {
href: "http://localhost:1111/accounts"
},
accountHolder: {
href: "http://localhost:1111/accounts/1000/5115"
}
}
}

It also provides links to any account holder of any account detail of our api telling it the URLs it can use to:

http://localhost:1111/accounts/{id}/{accountHolderId}

{
userId: 5115,
name: "Arnav",
address: "Noida",
_links: {
account: {
href: "http://localhost:1111/accounts/1000"
}
}
}

This account holder detail api has link for associated account detail for this user

And account detail API has two links one for all account list and other for account holder details.

5.3 ResourceSupport Implementation
ResourceSupport is the representation of resources. This class allows us to add the links and manage it.

/**
 * 
 */
package com.doj.hateoas.ws.accounts;

import java.io.Serializable;

import org.springframework.hateoas.ResourceSupport;

/**
 * @author Dinesh.Rajput
 *
 */
public class Account extends ResourceSupport implements Serializable {
 
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private Long accountId;
 private AccountHolder accountHolder;
 private Double amount;
 private String ifscCode;
 public Long getAccountId() {
  return accountId;
 }
 public void setAccountId(Long accountId) {
  this.accountId = accountId;
 }
 public AccountHolder getAccountHolder() {
  return accountHolder;
 }
 public void setAccountHolder(AccountHolder accountHolder) {
  this.accountHolder = accountHolder;
 }
 public Double getAmount() {
  return amount;
 }
 public void setAmount(Double amount) {
  this.amount = amount;
 }
 public String getIfscCode() {
  return ifscCode;
 }
 public void setIfscCode(String ifscCode) {
  this.ifscCode = ifscCode;
 }
 public Account(Long accountId, AccountHolder accountHolder, Double amount, String ifscCode) {
  super();
  this.accountId = accountId;
  this.accountHolder = accountHolder;
  this.amount = amount;
  this.ifscCode = ifscCode;
 }
 @Override
 public String toString() {
  return "Account [accountId=" + accountId + ", accountHolder=" + accountHolder + ", amount=" + amount
    + ", ifscCode=" + ifscCode + "]";
 }
}

6. Summary
Here we have been learned about Spring HATEOAS one of different approach to creating Restful web service. And also a build an application to describe HATEOAS concept.