HATEOAS driven REST API using Spring Boot

Introduction

In this post we will see an example on HATEOAS driven REST API using Spring Boot. An approach that breaks down the principal elements of a REST approach into three steps are resources, http verbs and hypermedia controls:

  • Without using any mechanism of the web, HTTP is used as a transport system for remote interactions.
  • Every individual service end point in REST is treated as resource.
  • Every operation is performed on HTTP protocol using HTTP verbs such as, GET, POST, PUT, DELETE, etc.
  • Finally, comes hypermedia control, where a list of related links is returned in the response of the resource, which is known as HATEOAS.

What is HATEOAS?

HATEOAS is an acronym for Hypermedia As The Engine Of Application State. HATEOAS is an extra level on REST (REpresentational State Transfer) API and is used to present information about the REST API to the client, allowing for a better understanding of the API without the need to bring up the specification or documentation. This is done by including the related links in a returned response from a resource in REST API and using only these links to further communicate with the sever. So the representations returned for REST resources contain not only data, but links to related resources. It’s assumed that the links returned with the response have already implemented the standard REST verbs.

An example may be given by the following image:

hateoas driven rest api using spring boot

Prerequisites

Eclipse, Java 1.8, Gradle 4.10.2, Spring Boot 2.1.5

Example with Source Code

Creating Project

Create a gradle based project in Eclipse, the project structure will look similar to the below image:

hateoas driven rest api using spring boot

Updating Build Script

Replace the content of the default generated build.gradle script to include the required dependencies for our application.

buildscript {
	ext {
		springBootVersion = '2.1.4.RELEASE'
	}
    repositories {
                mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenLocal()
    mavenCentral()
}
    
dependencies {
	compile("org.springframework.boot:spring-boot-starter-hateoas:${springBootVersion}")
	compile("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
	runtime("com.h2database:h2:1.4.196")
}

In the above build script we have used h2 in-memory database to store or to retrieve data as required.

Creating Properties File

Create a property file called application.yml under classpath directory src/main/resources with the below content to enable h2 console and log the SQL statement used for database operations in pretty format in the console.

We will let you know about default-property-inclusion later in this post.

spring:
   jpa:
      show-sql: true
   h2:
      console:
         enabled: true
   jackson:
      default-property-inclusion: NON_NULL

Creating Entity Class

We need an entity class to map our Java attribute to database table’s column.

We will not create any SQL script for creating table in h2 database but Spring Boot will automatically create the table for below entity class.

If we do not specify the column name for the corresponding Java attribute or field then the same attribute name will be used to create column in the database table.

You don’t need to define any datasource when you are using in-memory embedded database h2.

package com.jeejava.spring.hateoas.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	private Integer empId;
	private String name;
	@Column(name = "email_address")
	private String email;

	public Employee() {
	}

	public Employee(Integer empId, String name, String email) {
		this.empId = empId;
		this.name = name;
		this.email = email;
	}

	public Integer getEmpId() {
		return empId;
	}

	public void setEmpId(Integer empId) {
		this.empId = empId;
	}

	public String getName() {
		return name;
	}

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

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

Inserting Data into Table

Spring Boot will create table from entity class but we need to insert some data into the table.

So we will create a SQL script called data.sql under classpath directory src/main/resources as shown below:

insert into employee(name,email_address)
values('Soumitra','soumitra@email.com');
insert into employee(name,email_address)
values('Liton','liton@email.com');
insert into employee(name,email_address)
values('Suman','suman@email.com');
insert into employee(name,email_address)
values('Debabrata','debabrata@email.com');

Creating JPA Repository

Spring has excellent API called Spring Data JPA Repository that gives us many benefits out of the box. You just need to write methods by extending JpaRepository interface for performing basic CRUD operations. Then Spring will generate appropriate queries for your methods.

In the below repository interface we have defined three methods for retrieving an employee’s name in three ways – by employee id, by employee name or by employee email address.

package com.jeejava.spring.hateoas.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.jeejava.spring.hateoas.entity.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

	Employee findById(int id);

	Employee findByName(String name);

	Employee findByEmail(String email);

}

Creating VO Class

It is not good idea to use entity class as a response object from REST or Service class to avoid unexpected issues (for example, stack overflow error).

So we will create below VO or DTO class to send as a response object from service class and later it will be converted into appropriate JSON response to the end users.

The below class extends ResourceSupport, which supports Spring HATEOAS and allows you to add instances of Link in the response. Now the below class is an example of resource representation class. Without using ResourceSupport class from Spring framework you will not be able to create related links for your resources.

package com.jeejava.spring.hateoas.vo;

import org.springframework.hateoas.ResourceSupport;

public class EmployeeVo extends ResourceSupport {

	private Integer empId;
	private String name;
	private String email;

	public Integer getEmpId() {
		return empId;
	}

	public void setEmpId(Integer empId) {
		this.empId = empId;
	}

	public String getName() {
		return name;
	}

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

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

Creating Service Class

Need Spring service class to perform business logic. We will perform basic CRUD operations on an employee object and we will simply convert entity object into vo object and send to the REST class.

package com.jeejava.spring.hateoas.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.jeejava.spring.hateoas.entity.Employee;
import com.jeejava.spring.hateoas.repository.EmployeeRepository;
import com.jeejava.spring.hateoas.vo.EmployeeVo;

@Service
public class EmployeeService {

	@Autowired
	private EmployeeRepository repository;

	public List<EmployeeVo> getEmployeeList() {
		List<Employee> employees = repository.findAll();

		return employees.stream().map(e -> {
			EmployeeVo vo = new EmployeeVo();
			vo.setEmpId(e.getEmpId());
			vo.setName(e.getName());
			vo.setEmail(e.getEmail());
			return vo;
		}).collect(Collectors.toList());
	}

	public EmployeeVo getEmployeeById(int id) {
		EmployeeVo employeeVo = new EmployeeVo();

		Employee employee = repository.findById(id);

		employeeVo.setEmpId(employee.getEmpId());
		employeeVo.setName(employee.getName());
		employeeVo.setEmail(employee.getEmail());

		return employeeVo;
	}

	public EmployeeVo getEmployeeByName(String name) {
		EmployeeVo employeeVo = new EmployeeVo();

		Employee employee = repository.findByName(name);

		employeeVo.setEmpId(employee.getEmpId());
		employeeVo.setName(employee.getName());
		employeeVo.setEmail(employee.getEmail());

		return employeeVo;
	}

	public EmployeeVo getEmployeeByEmail(String email) {
		EmployeeVo employeeVo = new EmployeeVo();

		Employee employee = repository.findByEmail(email);

		employeeVo.setEmpId(employee.getEmpId());
		employeeVo.setName(employee.getName());
		employeeVo.setEmail(employee.getEmail());

		return employeeVo;
	}

	public void saveEmployee(EmployeeVo employeeVo) {
		Employee employee = new Employee();
		employee.setName(employeeVo.getName());
		employee.setEmail(employeeVo.getEmail());
		repository.save(employee);
	}

	public void updateEmployee(EmployeeVo employeeVo) {
		Employee employee = new Employee();
		employee.setEmpId(employeeVo.getEmpId());
		employee.setName(employeeVo.getName());
		employee.setEmail(employeeVo.getEmail());
		repository.save(employee);
	}

	public void deleteEmployeeById(int id) {
		repository.deleteById(id);
	}

}

Creating REST Controller

The Spring REST Controller class is responsible for handling request/response for the end users or clients.

Here we will see how we will build the related links for the response object.

We have added links using Spring’s ControllerLinkBuilder class. You can also import this class as a static import. We have built links for GET requests only.

package com.jeejava.spring.hateoas.rest.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.jeejava.spring.hateoas.service.EmployeeService;
import com.jeejava.spring.hateoas.vo.EmployeeVo;

@RestController
public class EmployeeRestController {

	@Autowired
	private EmployeeService service;

	@GetMapping("/employees")
	public ResponseEntity<List<EmployeeVo>> getEmployeeList() {
		List<EmployeeVo> list = service.getEmployeeList();

		list.forEach(ev -> {
			ev.add(ControllerLinkBuilder
					.linkTo(ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeById(ev.getEmpId()))
					.withRel("employee-by-id"));
			ev.add(ControllerLinkBuilder.linkTo(
					ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeByName(ev.getName()))
					.withRel("employee-by-name"));
			ev.add(ControllerLinkBuilder.linkTo(
					ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeByEmail(ev.getEmail()))
					.withRel("employee-by-email"));
		});

		return new ResponseEntity<List<EmployeeVo>>(list, HttpStatus.OK);
	}

	@GetMapping("/employee/id/{id}")
	public ResponseEntity<EmployeeVo> getEmployeeById(@PathVariable int id) {
		EmployeeVo emp = service.getEmployeeById(id);

		emp.add(ControllerLinkBuilder
				.linkTo(ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeList())
				.withRel("employee-list"));
		emp.add(ControllerLinkBuilder
				.linkTo(ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeById(id))
				.withSelfRel());
		emp.add(ControllerLinkBuilder
				.linkTo(ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeByName(emp.getName()))
				.withRel("employee-by-name"));
		emp.add(ControllerLinkBuilder
				.linkTo(ControllerLinkBuilder.methodOn(EmployeeRestController.class).getEmployeeByEmail(emp.getEmail()))
				.withRel("employee-by-email"));

		return new ResponseEntity<EmployeeVo>(emp, HttpStatus.OK);
	}

	@GetMapping("/employee/name/{name}")
	public ResponseEntity<EmployeeVo> getEmployeeByName(@PathVariable String name) {
		EmployeeVo emp = service.getEmployeeByName(name);

		// ...you can add relevant links

		return new ResponseEntity<EmployeeVo>(emp, HttpStatus.OK);
	}

	@GetMapping("/employee/email/{email}")
	public ResponseEntity<EmployeeVo> getEmployeeByEmail(@PathVariable String email) {
		EmployeeVo emp = service.getEmployeeByEmail(email);

		// ...you can add relevant links

		return new ResponseEntity<EmployeeVo>(emp, HttpStatus.OK);
	}

	@PostMapping("/employee")
	public ResponseEntity<Void> saveEmployee(@RequestBody EmployeeVo employeeVo) {
		service.saveEmployee(employeeVo);

		return new ResponseEntity<Void>(HttpStatus.OK);
	}

	@PutMapping("/employee")
	public ResponseEntity<Void> updateEmployee(@RequestBody EmployeeVo employeeVo) {
		service.updateEmployee(employeeVo);

		return new ResponseEntity<Void>(HttpStatus.OK);
	}

	@DeleteMapping("/employee/{id}")
	public ResponseEntity<Void> deleteEmployee(@PathVariable int id) {
		service.deleteEmployeeById(id);

		return new ResponseEntity<Void>(HttpStatus.OK);
	}

}

Creating Configuration Class

Create below configuration class to let Spring container know where our entity and repository classes.

package com.jeejava.spring.hateoas.config;

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EntityScan("com.jeejava.spring.hateoas.entity")
@EnableJpaRepositories("com.jeejava.spring.hateoas.repository")
public class SpringConfig {

}

Creating Main Class

Create below main class to deploy the application into embedded Tomcat server:

package com.jeejava.spring.hateoas;

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

@SpringBootApplication(scanBasePackages = "com.jeejava.spring.hateoas")
public class SpringHateoas {

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

}

Testing the Application

Run the main class to deploy the application into Tomcat server.

Hit the URL to get the HATEOAS with response as shown in the below image:

hateoas driven rest api using spring boot

We said that we will explain why we are using spring.jackson.default-property-inclusion=NON_NULL in properties file. So if you do not configure this variable then you will get below output with null values for few fields in the JSON response.

Probably you don’t want to include the fields which have null values and to avoid it we are using the said property.

hateoas driven rest api

Source Code

You can download source code.

Thanks for reading.

HATEOAS driven REST API using Spring Boot

One thought on “HATEOAS driven REST API using Spring Boot

Leave a Reply

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

Scroll to top