HATEOAS driven REST API using Spring Boot

Introduction

In this post you 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

Java at least 1.8, Gradle 4.10.2/6.8.3, Maven 3.6.3, Spring Boot 2.4.5

Project Setup

Create a gradle or maven based project in your favorite tool or IDE. The name of the project is spring-hateoas.

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

buildscript {
	ext {
		springBootVersion = '2.4.5'
	}
    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.200")
}

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

If you are using maven based project then you can use the following pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-hateoas</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>12</maven.compiler.source>
		<maven.compiler.target>12</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-hateoas</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

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.

I 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

Entity Class

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

I 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 I 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.roytuts.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;
	}
}

Dump Data into Table

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

So I 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');

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 I 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.roytuts.spring.hateoas.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.roytuts.spring.hateoas.entity.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
	Employee findById(int id);
	Employee findByName(String name);
	Employee findByEmail(String email);
}

DTO or 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 I 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 EntityModel<T>, 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 EntityModel<T> class from Spring framework you will not be able to create related links for your resources.

package com.roytuts.spring.hateoas.vo;

import org.springframework.hateoas.EntityModel;

public class EmployeeVo extends EntityModel<EmployeeVo> {

	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;
	}

}

Service Class

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

package com.roytuts.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.roytuts.spring.hateoas.entity.Employee;
import com.roytuts.spring.hateoas.repository.EmployeeRepository;
import com.roytuts.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);
	}
}

REST Controller

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

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

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

package com.roytuts.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.roytuts.spring.hateoas.service.EmployeeService;
import com.roytuts.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);
	}
}

Configuration Class

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

package com.roytuts.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.roytuts.spring.hateoas.entity")
@EnableJpaRepositories("com.roytuts.spring.hateoas.repository")
public class SpringConfig {
}

Main Class

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

package com.roytuts.spring.hateoas;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.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

I already said that I will explain why am I 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, I am using the said property.

hateoas driven rest api

Source Code

Download

1 thought on “HATEOAS driven REST API using Spring Boot

Leave a Reply

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