Master java skills

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]');

Test the service

Report Url