Spring Boot - HATEOAS for RESTful Services


Image Image


This guide will help you implement HATEOAS for a REST API/Service with Spring Boot.

You will learn

  • What is HATEOAS?
  • Why do you need HATEOAS?
  • How do you implement HATEOAS with Spring Boot?
  • What are the HATEOAS best practices?

Free Courses - Learn in 10 Steps

Project Code Structure

Following screenshot shows the structure of the project we will create. Image

A few details:

  • SpringBoot2RestServiceApplication.java - The Spring Boot Application class generated with Spring Initializer. This class acts as the launching point for application.
  • pom.xml - Contains all the dependencies needed to build this project. We will use Spring Boot Starter AOP.
  • Student.java - Student JPA Entity
  • StudentRepository.java - Student JPA Repository. This is created using Spring Data JpaRepository.
  • StudentResource.java - Spring Rest Controller exposing all services on the student resource.
  • data.sql - Initial data for the student table. Spring Boot would execute this script after the tables are created from the entities.

Tools you will need

  • Maven 3.0+ is your build tool
  • Your favorite IDE. We use Eclipse.
  • JDK 1.8+

Complete Maven Project With Code Examples

Our Github repository has all the code examples - https://github.com/in28minutes/spring-boot-examples/tree/master/spring-boot-2-rest-service-hateoas

Richardson Maturity Model

Richardson Maturity Model defines the maturity level of a Restful Web Service. Following are the different levels and their characteristics.

  • Level 0 : Expose SOAP web services in REST style. Expose action based services (http://server/getPosts, http://server/deletePosts, http://server/doThis, http://server/doThat etc) using REST.
  • Level 1 : Expose Resources with proper URI’s (using nouns). Ex: http://server/accounts, http://server/accounts/10. However, HTTP Methods are not used.
  • Level 2 : Resources use proper URI’s + HTTP Methods. For example, to update an account, you do a PUT to . The create an account, you do a POST to . Uri’s look like posts/1/comments/5 and accounts/1/friends/1.
  • Level 3 : HATEOAS (Hypermedia as the engine of application state). You will tell not only about the information being requested but also about the next possible actions that the service consumer can do. When requesting information about a facebook user, a REST service can return user details along with information about how to get his recent posts, how to get his recent comments and how to retrieve his friend’s list.

What is HATEOAS?

HATEOAS stands for “Hypermedia as the engine of application state”

Its a complicated acronym. Let’s decode it for you.

What do you see when you visit a web page?

The data that you would want to see. Is that all? You would also see links and buttons to see related data.

For example, if you go to a student page, you will see

  • Student profile
  • Links to Edit and Delete Student details
  • Links to see details of other students
  • Link to see details of the courses and grades of the student

HATEOAS brings the same concepts to RESTful Web Services.

When some details of a resource are requested, you will provide the resource details as well as details of related resources and the possible actions you can perform on the resource. For example, when requesting information about a facebook user, a REST service can return the following

  • User details
  • Links to get his recent posts
  • Links to get his recent comments
  • Links to retrieve his friend’s list.

Bootstrapping a Project with a REST Resource

In the previous article in the series - http://www.springboottutorial.com/spring-boot-crud-rest-service-with-jpa-hibernate, we set up a simple restful service with a resource exposing CRUD methods.

We will use the same example to discuss about HATEOAS.

Implementing HATEOAS with Spring Boot

Spring Boot provides a Starter for HATEOAS. Include the dependency in your pom.xml

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

Components in Spring Boot HATEOAS Starter

Listed below are some of the important dependencies from spring-boot-starter-hateoas.

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.0.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>0.24.0.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.plugin</groupId>
    <artifactId>spring-plugin-core</artifactId>
    <version>1.2.0.RELEASE</version>
    <scope>compile</scope>
  </dependency>

Most important dependency is spring-hateoas.

Enhancing the resource to return HATEOAS response

To implement HATEOAS, we would need to include related resources in the response.

Instead of Student we use a return type of Resource<Student>.

Resource is a simple class wrapping a domain object and allows adding links to it.

@GetMapping("/students/{id}")
public Resource<Student> retrieveStudent(@PathVariable long id) {
  Optional<Student> student = studentRepository.findById(id);

  if (!student.isPresent())
    throw new StudentNotFoundException("id-" + id);

  Resource<Student> resource = new Resource<Student>(student.get());

  ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());

  resource.add(linkTo.withRel("all-students"));

  return resource;
}

We create a new resource.

  Resource<Student> resource = new Resource<Student>(student.get());

We add the link to retrieve all students method to the links.

  ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());

  resource.add(linkTo.withRel("all-students"));

The response when we execute a GET request to http://localhost:8080/students/10001 is shown below:

{
  "id": 10001,
  "name": "Ranga",
  "passportNumber": "E1234567",
  "_links": {
    "all-students": {
      "href": "http://localhost:8080/students"
    }
  }
}

You can see that there is a new section _links with a link to all students Resource.

Enhance Other Resources with HATEOAS

Above example covers important concepts in enhancing resources with HATEOAS.

However, you have to make the important decision:

  • What are the important resources related to a specific resource?

Go ahead and enhance the application with more HATEOAS links.

Complete Code Example

/pom.xml

<?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.in28minutes.springboot.rest.example</groupId>
  <artifactId>spring-boot-2-rest-service-hateoas</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>spring-boot-2-rest-service</name>
  <description>Spring Boot 2 and REST - Example Project</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

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

  <repositories>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
    <pluginRepository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>


</project>

/src/main/java/com/in28minutes/springboot/rest/example/SpringBoot2RestServiceApplication.java

package com.in28minutes.springboot.rest.example;

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

@SpringBootApplication
public class SpringBoot2RestServiceApplication {

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

/src/main/java/com/in28minutes/springboot/rest/example/student/Student.java

package com.in28minutes.springboot.rest.example.student;

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

@Entity
public class Student {
  @Id
  @GeneratedValue
  private Long id;
  private String name;
  private String passportNumber;
  
  public Student() {
    super();
  }

  public Student(Long id, String name, String passportNumber) {
    super();
    this.id = id;
    this.name = name;
    this.passportNumber = passportNumber;
  }
  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;
  }
  public String getPassportNumber() {
    return passportNumber;
  }
  public void setPassportNumber(String passportNumber) {
    this.passportNumber = passportNumber;
  }
    
}

/src/main/java/com/in28minutes/springboot/rest/example/student/StudentNotFoundException.java

package com.in28minutes.springboot.rest.example.student;

public class StudentNotFoundException extends RuntimeException {

  public StudentNotFoundException(String exception) {
    super(exception);
  }

}

/src/main/java/com/in28minutes/springboot/rest/example/student/StudentRepository.java

package com.in28minutes.springboot.rest.example.student;

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

@Repository
public interface StudentRepository extends JpaRepository<Student, Long>{

}

/src/main/java/com/in28minutes/springboot/rest/example/student/StudentResource.java

package com.in28minutes.springboot.rest.example.student;

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

import java.net.URI;
import java.util.List;
import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
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 org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@RestController
public class StudentResource {

  @Autowired
  private StudentRepository studentRepository;

  @GetMapping("/students")
  public List<Student> retrieveAllStudents() {
    return studentRepository.findAll();
  }

  @GetMapping("/students/{id}")
  public Resource<Student> retrieveStudent(@PathVariable long id) {
    Optional<Student> student = studentRepository.findById(id);

    if (!student.isPresent())
      throw new StudentNotFoundException("id-" + id);

    Resource<Student> resource = new Resource<Student>(student.get());

    ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());

    resource.add(linkTo.withRel("all-students"));

    return resource;
  }

  @DeleteMapping("/students/{id}")
  public void deleteStudent(@PathVariable long id) {
    studentRepository.deleteById(id);
  }

  @PostMapping("/students")
  public ResponseEntity<Object> createStudent(@RequestBody Student student) {
    Student savedStudent = studentRepository.save(student);

    URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
        .buildAndExpand(savedStudent.getId()).toUri();

    return ResponseEntity.created(location).build();

  }
  
  @PutMapping("/students/{id}")
  public ResponseEntity<Object> updateStudent(@RequestBody Student student, @PathVariable long id) {

    Optional<Student> studentOptional = studentRepository.findById(id);

    if (!studentOptional.isPresent())
      return ResponseEntity.notFound().build();

    student.setId(id);
    
    studentRepository.save(student);

    return ResponseEntity.noContent().build();
  }
}

/src/main/resources/application.properties


/src/main/resources/data.sql

insert into student
values(10001,'Ranga', 'E1234567');

insert into student
values(10002,'Ravi', 'A1234568');

/src/test/java/com/in28minutes/springboot/rest/example/SpringBoot2RestServiceApplicationTests.java

package com.in28minutes.springboot.rest.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBoot2RestServiceApplicationTests {

  @Test
  public void contextLoads() {
  }

}

Related Posts

Docker Tutorial for Beginners - with Java and Spring Boot

Learn about Docker - What is Docker? Why Is Docker Popular? How to create Docker Image for a Java Spring Boot App?

Devops Tutorial | DevOps with Docker, Kubernetes and Azure DevOps

What is DevOps? How is it different from Agile? What are the popular DevOps Tools? What is the role of Docker, Kubernetes and Azure DevOps in DevOps. Let's get started with a simple usecase.

Deploy Java Spring Boot Applications to AWS, Azure, GCP with Docker and Kubernetes

In this article, we focus our attention on the cloud. How to learn the cloud and deploy Java Spring Boot Applications to AWS, Azure, GCP with Docker and Kubernetes?

Spring Boot Tutorials for Beginners

At in28Minutes, we are creating a number of tutorials with videos, articles & courses on Spring Boot for Beginners and Experienced Developers. This resources will help you learn and gain expertise at Spring Boot.

Microservices with Spring Boot and Java - Part 1 - Getting Started

Let's learn the basics of microservices and microservices architectures. We will also start looking at a basic implementation of a microservice with Spring Boot. We will create a couple of microservices and get them to talk to each other using Eureka Naming Server and Ribbon for Client Side Load Balancing. In part 1 of this series, lets get introduced to the concept of microservices and understand how to create great microservices with Spring Boot and Spring Cloud.

20+ Spring Boot Projects with Code Examples

At in28Minutes, we have created more than 20 projects with code examples on Github. We have 50+ articles explaining these projects. These code examples will you learn and gain expertise at Spring Boot.

REST API Best Practices - With Design Examples from Java and Spring Web Services

Designing Great REST API is important to have great microservices. How do you design your REST API? What are the best practices?

Index - 500+ Videos

At in28Minutes, we are creating a number of tutorials with videos, articles & courses on Spring Boot for Beginners and Experienced Developers. Here's a list of video tutorials and courses for you

Creating Spring Boot and React Java Full Stack Application with Maven

This guide helps you create a Java full stack application with all the CRUD (Create, Read, Update and Delete) features using React as Frontend framework and Spring Boot as the backend REST API. We use Maven as the build tool.

Creating a SOAP Web Service with Spring Boot Starter Web Services

Let's learn how to create a SOAP Web Service with Spring Boot Starter Web Services. We will take a Contract First approach by definining an XSD and exposing a WSDL from it.

FREE COURSES



in28Minutes Best Selling Udemy Courses

Image Image Image Image Image Image Image Image Image

450,000 Learners are pursuing our 31 amazing courses and 6 Learning Paths. Start Now!

Join 450,000 Learners and 30+ Amazing Courses

LEARN DEVOPS - 200+ VIDEOS - Master DevOps with Docker, Kubernetes and Azure DevOps


350,000 Learners are learning everyday with our Best Selling Courses : Spring Boot Microservices, Spring, Spring Boot, Web Services, Hibernate, Full Stack React, Full Stack Angular, Python, Spring Interview Guide, Java Interview, Java Functional Programming, AWS, Docker, Kubernetes, PCF, AWS Fargate and Azure


85,000 Subscribers are learning from our Free Videos on YouTube : JSP Servlets, Spring, Spring Boot, Spring MVC, Hibernate, Eclipse, Maven, JUnit, Mockito, Full Stack - React, Full Stack - Angular, Docker, Kubernetes, AWS, AWS Fargate, PCF and Azure


Here are the recommend articles to read next : Spring Interview Questions, Spring Boot Interview Questions, Microservices, Hibernate, Spring Security, REST API with Spring Boot, Full Stack with React, SOAP Web Services, Exception Handling, Embedded Servers, Spring Data Rest, Spring vs Spring MVC vs Spring Boot, Building Web Application and Spring Data JPA.

You can checkout all our 100+ articles here - All Articles.


Do not know where to start your learning journey? Check out our amazing learning paths:
Learning Path 01 - Spring and Spring Boot Web Applications and API Developer,
Learning Path 02 - Full Stack Developer with Spring Boot, React & Angular,
Learning Path 03 - Cloud Microservices Developer with Docker and Kubernetes,
Learning Path 04 - Learn Cloud with Spring Boot, AWS, Azure and PCF and
Learning Path 05 - Learn AWS with Microservices, Docker and Kubernetes


Subscribe