Spring Boot Hateoas Links Example
HATEOAS is abbreviation for Hypermedia as the Engine of Application State. It is a component of RESTful API architecture. With conventional approach there is no way to interact with the server from the response of a request to the endpoint. HATEOAS is the solution for this.
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath></relativePath>
</parent>
<groupId>com.jts</groupId>
<artifactId>hateoas-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hateoas-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Student.java
package com.jts;
import org.springframework.hateoas.RepresentationModel;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.xml.bind.annotation.XmlRootElement;
@Entity
@XmlRootElement(name = "student")
@Table(name = "student")
public class Student extends RepresentationModel<Student> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String firstName;
private String lastName;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Student(Integer id, String firstName) {
super();
this.id = id;
this.firstName = firstName;
}
public Student() {
}
}
StudentList.java
package com.jts;
import java.util.ArrayList;
import java.util.List;
import org.springframework.hateoas.RepresentationModel;
public class StudentList extends RepresentationModel<StudentList> {
private List<Student> students = new ArrayList<Student>();
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
}
StudentReportResult.java
package com.jts;
import org.springframework.hateoas.RepresentationModel;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "student-report")
public class StudentReportResult extends RepresentationModel<StudentReportResult> {
private String overallReport;
public String getOverallReport() {
return overallReport;
}
public void setOverallReport(String overallReport) {
this.overallReport = overallReport;
}
}
StudentRepository.java
package com.jts;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
StudentController.java
package com.jts;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@Autowired
StudentRepository repository;
@GetMapping("/students")
public StudentList getAllStudents() {
StudentList studentList = new StudentList();
for (Student student : repository.findAll()) {
addLinkToStudent(student);
studentList.getStudents().add(student);
}
// Adding self link student collection resource
Link selfLink = linkTo(methodOn(StudentController.class).getAllStudents()).withSelfRel();
studentList.add(selfLink);
return studentList;
}
@GetMapping("/students/{id}")
public ResponseEntity<Student> getStudentById(@PathVariable("id") int id) {
Optional<Student> studentOpt = repository.findById(id);
if (studentOpt.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Student student = studentOpt.get();
addLinkToStudent(student);
return new ResponseEntity<>(student, HttpStatus.OK);
}
@GetMapping("/students/{id}/report")
public ResponseEntity<StudentReportResult> getReportByStudentById(@PathVariable("id") int id) {
StudentReportResult report = new StudentReportResult();
report.setOverallReport("The student is sincere and very interactive in the class");
return new ResponseEntity<>(report, HttpStatus.OK);
}
private void addLinkToStudent(Student student) {
// Adding self link student 'singular' resource
Link link = linkTo(StudentController.class).slash("students").slash(student.getId()).withSelfRel();
student.add(link);
// Adding method link student 'singular' resource
ResponseEntity<StudentReportResult> methodLinkBuilder = methodOn(StudentController.class)
.getReportByStudentById(student.getId());
Link reportLink = linkTo(methodLinkBuilder).withRel("student-report");
student.add(reportLink);
}
}
application.properties file
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.h2.console.enabled=true
Create data.sql file under src/main/resources folder
insert into student (id, first_Name, last_Name, email) values (101, 'Arjun', 'Kumar', '[email protected]');
insert into student (id, first_Name, last_Name, email) values (102, 'Shankar', 'Kumar', '[email protected]');
insert into student (id, first_Name, last_Name, email) values (103, 'Javed', 'Khan', '[email protected]');