How to start Spring HATEOAS RestAPI with Spring Boot

Spring HATEOAS project helps to create REST representations that follow HATEOAS (Hypertext as the Engine of Application State) principle. In this article, we’re gonna look at way to build a Rest Service using Spring HATEOAS with Spring Boot.

I. Overview

1. HATEOAS

In general, HATEOAS principle implies that with each response, API should shows Clients appropriate information about the next potential steps or guide to deeper understanding itself.

For example, if client requests for specific Customer information:
http://localhost:8080/customers/68
The Service should provide link to get all Orders of that Customer in the response:

{
   ...
   "customerId": 68,
   "_links": {
      "self": {
         "href": "http://localhost:8080/customers/68"
      },
      "allOrders": {
         "href": "http://localhost:8080/customers/68/orders"
      }
   }
}
2. Spring HATEOAS

Spring HATEOAS provides a set of useful types to ease working with those.

Firstly, we need create a Link object:
– Way 1: using Spring HATEOAS ControllerLinkBuilder:

Link link = linkTo(YourController.class).slash(id).withSelfRel();
/*
      "self": {
         "href": "[controller uri]/id"
      }
*/

Link link = linkTo(YourController.class).slash(id).withRel("yourrel"); 
/*
      "yourrel": {
         "href": "[controller uri]/id"
      }
*/

Link link = linkTo(methodOn(YourController.class).method(id)).withSelfRel();
/*
      "self": {
         "href": "[method uri]/id"
      }
*/

Link link = linkTo(methodOn(YourController.class).method(id)).withRel("yourrel"); 
/*
      "yourrel": {
         "href": "[method uri]/id"
      }
*/

– Way 2: using Spring HATEOAS EntityLinks:

@ExposesResourceFor(YourClass.class) class YourController

// pointing to the collection resource of the given type, relation type is defaulted to Link.REL_SELF.
Link link = entityLinks.linkToCollectionResource(YourClass.class);
Link link = entityLinks.linkToCollectionResource(YourClass.class).withRel("yourrel"));

// pointing to the single resource, relation type is defaulted to Link.REL_SELF.
Link link = entityLinks.linkToSingleResource(YourClass.class, id);
Link link = entityLinks.linkToSingleResource(YourClass.class, id).withRel("yourrel"));

Then, add Link object to ResourceSupport object:
– Way 1: extending ResourceSupport class

class YourClass extends ResourceSupport { ... }

yourClass.add(Link);

– Way 2: using Resources (that extends ResourceSupport class)

class YourClass { ... }

// for collection resource
Resources resources = ...;
resources.add(Link);

// for single resource
Resource resource = ...;
resource.add(Link);

Finally, just return YourClass object or Resource object to Client.

II. Practice

1. Technology

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.4.RELEASE
– Spring Boot: 1.5.4.RELEASE

2. Project Overview

spring-hateoas-structure

Controller -> Repository -> Model:
+ CustomerController (@ExposesResourceFor(...)) -> CustomerRepository -> Customer(id, name, orders)
+ PersonController -> PersonRepository -> Person(id, name, orders) extends ResourceSupport

Dependency for Spring HATEOAS in pom.xml.

3. Step by step
3.1 Create Spring Boot project

Using Spring Tool Suite/Eclipse to create Project and add Dependencies to pom.xml file:


	org.springframework.boot
	spring-boot-starter-hateoas

3.2 Create Data Model Classes
package com.javasampleapproach.hateoas.model;

public class Order {

	private Long id;
	private String name;

	public Order(Long id, String name) {
		this.id = id;
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}
package com.javasampleapproach.hateoas.model;

import java.util.ArrayList;
import java.util.List;

public class Customer {

	private Long id;
	private String name;

	private List orders;

	public Customer(Long id, String name, ArrayList orders) {
		this.id = id;
		this.name = name;
		this.orders = new ArrayList(orders);
	}

	public Long getCustomerId() {
		return id;
	}

	public void setCustomerId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List getOrders() {
		return orders;
	}

	public void setOrders(List orders) {
		this.orders = orders;
	}

}
package com.javasampleapproach.hateoas.model;

import java.util.ArrayList;
import java.util.List;

import org.springframework.hateoas.ResourceSupport;

public class Person extends ResourceSupport {

	private Long id;
	private String name;

	private List orders;

	public Person(Long id, String name, ArrayList orders) {
		this.id = id;
		this.name = name;
		this.orders = new ArrayList(orders);
	}

	public Long getPersonId() {
		return id;
	}

	public void setPersonId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List getOrders() {
		return orders;
	}

	public void setOrders(List orders) {
		this.orders = orders;
	}

}
3.3 Create Repository Classes
package com.javasampleapproach.hateoas.repo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.javasampleapproach.hateoas.model.Customer;
import com.javasampleapproach.hateoas.model.Order;

@Repository
public class CustomerRepository {

	private final List customers = new ArrayList<>();

	public CustomerRepository() {
		this.customers.add(
				new Customer(1L, "Jack", 
						new ArrayList(Arrays.asList(new Order(1L, "A"), new Order(2L, "B")))
							));
		this.customers.add(
				new Customer(2L, "Adam", 
						new ArrayList(Arrays.asList(new Order(1L, "C"), new Order(2L, "D")))
							));
		this.customers.add(
				new Customer(3L, "Kim", 
						new ArrayList(Arrays.asList(new Order(1L, "E"), new Order(2L, "F")))
							));
	}

	public List findAll() {
		return this.customers;
	}

	public Customer findOne(Long id) {

		for (Customer customer : this.customers) {
			if (customer.getCustomerId().equals(id)) {
				return customer;
			}
		}
		return null;
	}
}
package com.javasampleapproach.hateoas.repo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.javasampleapproach.hateoas.model.Order;
import com.javasampleapproach.hateoas.model.Person;

@Repository
public class PersonRepository {

	private final List persons = new ArrayList<>();

	public PersonRepository() {
		this.persons.add(
				new Person(1L, "Jack", new ArrayList(Arrays.asList(new Order(1L, "A"), new Order(2L, "B")))));
		this.persons.add(
				new Person(2L, "Adam", new ArrayList(Arrays.asList(new Order(1L, "C"), new Order(2L, "D")))));
		this.persons.add(
				new Person(3L, "Kim", new ArrayList(Arrays.asList(new Order(1L, "E"), new Order(2L, "F")))));
	}

	public List findAll() {
		return this.persons;
	}

	public Person findOne(Long id) {

		for (Person person : this.persons) {
			if (person.getPersonId().equals(id)) {
				return person;
			}
		}
		return null;
	}
}
3.4 Create Controller Classes
package com.javasampleapproach.hateoas.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
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.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;

import java.util.List;

import com.javasampleapproach.hateoas.model.Customer;
import com.javasampleapproach.hateoas.model.Order;
import com.javasampleapproach.hateoas.repo.CustomerRepository;

@RestController
@ExposesResourceFor(Customer.class)
@RequestMapping("/customers")
public class CustomerController {

	@Autowired
	private CustomerRepository repository;

	@Autowired
	private EntityLinks entityLinks;

	@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
	public Resources getCustomers() {

		Resources resources = new Resources<>(this.repository.findAll());
		resources.add(this.entityLinks.linkToCollectionResource(Customer.class));

		return resources;
	}

	@RequestMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
	public Resource getCustomer(@PathVariable Long id) {

		Customer customer = this.repository.findOne(id);

		Resource resource = new Resource<>(customer);
		resource.add(this.entityLinks.linkToSingleResource(Customer.class, id));
		resource.add(linkTo(methodOn(CustomerController.class).getOrdersForCustomer(id)).withRel("allOrders"));

		return resource;
	}

	@RequestMapping(path = "/{id}/orders")
	public List getOrdersForCustomer(@PathVariable Long id) {

		return this.repository.findOne(id).getOrders();
	}

}
package com.javasampleapproach.hateoas.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;

import java.util.List;

import com.javasampleapproach.hateoas.model.Person;
import com.javasampleapproach.hateoas.model.Order;
import com.javasampleapproach.hateoas.repo.PersonRepository;

@RestController
@RequestMapping("/persons")
public class PersonController {

	@Autowired
	private PersonRepository repository;

	@RequestMapping("")
	public List getAllPersons() {

		List persons = this.repository.findAll();

		persons.forEach(person -> {
			person.removeLinks();
			person.add(linkTo(PersonController.class).slash(person.getPersonId()).withSelfRel());
		});

		return persons;
	}

	@RequestMapping("/{id}/orders")
	public List getOrdersForPerson(@PathVariable Long id) {

		return this.repository.findOne(id).getOrders();
	}

	@RequestMapping("/{id}")
	public Person getTestPerson(@PathVariable Long id) {

		Person person = this.repository.findOne(id);

		person.removeLinks();
		person.add(linkTo(PersonController.class).slash(person.getPersonId()).withSelfRel());
		person.add(linkTo(methodOn(PersonController.class).getOrdersForPerson(id)).withRel("allOrders"));

		return person;
	}
}
3.5 Run & Check Result

– Config maven build:
clean install
– Run project with mode Spring Boot App
– Check results by open your browser and send request:

Request 1:
http://localhost:8080/customers

{
   "_embedded": {
      "customerList": [
         {
            "name": "Jack",
            "orders": [
               {
                  "id": 1,
                  "name": "A"
               },
               {
                  "id": 2,
                  "name": "B"
               }
            ],
            "customerId": 1
         },
         {
            "name": "Adam",
            "orders": [
               {
                  "id": 1,
                  "name": "C"
               },
               {
                  "id": 2,
                  "name": "D"
               }
            ],
            "customerId": 2
         },
         {
            "name": "Kim",
            "orders": [
               {
                  "id": 1,
                  "name": "E"
               },
               {
                  "id": 2,
                  "name": "F"
               }
            ],
            "customerId": 3
         }
      ]
   },
   "_links": {
      "self": {
         "href": "http://localhost:8080/customers"
      }
   }
}

Request 2:
http://localhost:8080/customers/2

{
   "name": "Adam",
   "orders": [
      {
         "id": 1,
         "name": "C"
      },
      {
         "id": 2,
         "name": "D"
      }
   ],
   "customerId": 2,
   "_links": {
      "self": {
         "href": "http://localhost:8080/customers/2"
      },
      "allOrders": {
         "href": "http://localhost:8080/customers/2/orders"
      }
   }
}

Request 3:
http://localhost:8080/persons

[
   {
      "name": "Jack",
      "orders": [
         {
            "id": 1,
            "name": "A"
         },
         {
            "id": 2,
            "name": "B"
         }
      ],
      "personId": 1,
      "links": [
         {
            "rel": "self",
            "href": "http://localhost:8080/persons/1"
         }
      ]
   },
   {
      "name": "Adam",
      "orders": [
         {
            "id": 1,
            "name": "C"
         },
         {
            "id": 2,
            "name": "D"
         }
      ],
      "personId": 2,
      "links": [
         {
            "rel": "self",
            "href": "http://localhost:8080/persons/2"
         }
      ]
   },
   {
      "name": "Kim",
      "orders": [
         {
            "id": 1,
            "name": "E"
         },
         {
            "id": 2,
            "name": "F"
         }
      ],
      "personId": 3,
      "links": [
         {
            "rel": "self",
            "href": "http://localhost:8080/persons/3"
         }
      ]
   }
]

Request 4:
http://localhost:8080/persons/2

{
   "name": "Adam",
   "orders": [
      {
         "id": 1,
         "name": "C"
      },
      {
         "id": 2,
         "name": "D"
      }
   ],
   "personId": 2,
   "_links": {
      "self": {
         "href": "http://localhost:8080/persons/2"
      },
      "allOrders": {
         "href": "http://localhost:8080/persons/2/orders"
      }
   }
}

III. Source Code

SpringBootHATEOAS

One thought on “How to start Spring HATEOAS RestAPI with Spring Boot”

Leave a Reply

Your email address will not be published. Required fields are marked *