In this Spring Boot HATEOAS example, we will learn to add HATEOAS (Hypertext as the Engine of Application State) links to REST API resource representations created in a Spring boot project.
1. Spring Boot HATEOAS Module
1.1. Dependency
In a spring boot project, we need to add the spring-boot-starter-hateoas module.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
If not using Spring boot, add the spring-hateoas and spring-plugin-core dependencies.
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.12.11</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
1.2. Core Classes
The HATEOAS module provides given three classes to add the links to the resource representations.
RepresentationModel
– Base class for DTOs to collect links.Link
– Immutable value object for links. It stores both a hypertext reference and a link relation. It exposes other attributes as defined in RFC-8288.WebMvcLinkBuilder
– Builder to ease building Link instances pointing to Spring MVC controllers.
1.3. Links
In general. a link contains an href and a rel attribute. Href points to the resource location and rel defines the semantics of the related resource. The media type information can be put into type attribute.
<link href="style.css" rel="stylesheet" type="text/css" />
1.3.1. Link Constructors
As stated earlier, hateoas module works with immutable Link
value type. Link.of()
is an overloaded method which takes various type of arguments to create an immutable instance of Link.
Link of(String href)
Link of(String href, LinkRelation relation)
Link of(String href, String relation)
Link of(UriTemplate template, LinkRelation relation)
Link of(UriTemplate template, String relation)
1.3.2. Creating Links
Lets see an example to see how Link instances are created.
Link link = Link.of("/employee-report");
assertThat(link.getHref()).isEqualTo("/employee-report");
assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF);
link = Link.of("/employee-report", IanaLinkRelations.RELATED);
assertThat(link.getHref()).isEqualTo("/employee-report");
assertThat(link.getRel()).isEqualTo(LinkRelation.of(IanaLinkRelations.RELATED));
Please note that the href value can be a URI templates as well and we can replace the placeholder values in runtime.
Link link = Link.of("/{department}/users/{?id}");
Map<String, Object> values = new HashMap<>();
values.put("department", "HR");
values.put("id", 123);
assertThat(link.expand(values).getHref())
.isEqualTo("/HR/users?id=123");
1.4. Link Relations
IanaLinkRelations class contains all the relation attribute values as per IANA (Internet Assigned Numbers Authority).
The default relation value is IanaLinkRelations.SELF. To change the link relation, we can use following code:
Link link = Link.of("/some-resource"), IanaLinkRelations.NEXT);
2. Creating Links with WebMvcLinkBuilder
Spring HATEOAS now provides a WebMvcLinkBuilder
that lets us create links by pointing to controller classes.
For example, our controller class is:
@RestController
public class EmployeeController {
@GetMapping("/employees")
public EmployeeListVO getAllEmployees() {
}
@GetMapping("/employees/{id}")
public ResponseEntity<EmployeeVO> getEmployeeById(@PathVariable("id") int id) {
}
@GetMapping("/employees/{id}/report")
public ResponseEntity<EmployeeReport> getReportByEmployeeById(@PathVariable("id") int id) {
}
}
2.1. Link.slash()
Now see how we can create various links to the controller and its methods using slash() method.
// Link to "/employees"
Link link = linkTo(EmployeeController.class).withRel("employees");
// Link to "/employees/{id}"
EmployeeVO e = new EmployeeVO(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com");
Link link = linkTo(EmployeeController.class).slash(e.getId()).withSelfRel();
2.2. Method Instances / WebMvcLinkBuilder.methodOn()
We can also use create a Method instance and pas it to WebMvcLinkBuilder.
Method method = EmployeeController.class.getMethod("getReportByEmployeeById", Integer.class);
Link link = linkTo(method, 123).withSelfRel();
//or
Link link = linkTo(methodOn(EmployeeController.class).getReportByEmployeeById(123)).withSelfRel();
3. Demo
In this example, I have created three APIs with endpoints as below:
- /employees
- /employees/{id}
- /employees/{id}/report
3.1. Project Structure

3.2. Resource Model
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.hateoas.RepresentationModel;
@XmlRootElement(name = "employees")
public class EmployeeListVO extends RepresentationModel<EmployeeListVO>
implements Serializable {
private static final long serialVersionUID = 1L;
private List<EmployeeVO> employees = new ArrayList<EmployeeVO>();
public List<EmployeeVO> getEmployees() {
return employees;
}
public void setEmployees(List<EmployeeVO> employees) {
this.employees = employees;
}
}
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.hateoas.RepresentationModel;
@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.NONE)
public class EmployeeVO extends RepresentationModel<EmployeeVO>
implements Serializable {
private static final long serialVersionUID = 1L;
public EmployeeVO(Integer id, String firstName, String lastName,
String email) {
super();
this.employeeId = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public EmployeeVO() {
}
@XmlAttribute
private Integer employeeId;
@XmlElement
private String firstName;
@XmlElement
private String lastName;
@XmlElement
private String email;
//Getters and setters are hidden for brevity
@Override
public String toString() {
return "EmployeeVO [id=" + employeeId + ", firstName=" + firstName
+ ", lastName=" + lastName + ", email=" + email + "]";
}
}
import java.io.Serializable;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.hateoas.RepresentationModel;
@XmlRootElement(name = "employee-report")
public class EmployeeReport extends RepresentationModel<EmployeeReport>
implements Serializable {
private static final long serialVersionUID = 1L;
// You can add field as needed
}
3.3. REST Controller Where We Add the HATEOAS Links
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
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;
import com.howtodoinjava.hateoas.demo.dao.EmployeeDB;
import com.howtodoinjava.hateoas.demo.model.EmployeeListVO;
import com.howtodoinjava.hateoas.demo.model.EmployeeReport;
import com.howtodoinjava.hateoas.demo.model.EmployeeVO;
@RestController
public class EmployeeController {
@GetMapping("/employees")
public EmployeeListVO getAllEmployees() {
EmployeeListVO employeesList = new EmployeeListVO();
for (EmployeeVO employee : EmployeeDB.getEmployeeList()) {
// Adding self link employee 'singular' resource
Link link = linkTo(EmployeeController.class)
.slash(employee.getEmployeeId()).withSelfRel();
// Add link to singular resource
employee.add(link);
// Adding method link employee 'singular' resource
ResponseEntity<EmployeeReport> methodLinkBuilder =
methodOn(EmployeeController.class)
.getReportByEmployeeById(employee.getEmployeeId());
Link reportLink =
linkTo(methodLinkBuilder).withRel("employee-report");
// Add link to singular resource
employee.add(reportLink);
employeesList.getEmployees().add(employee);
}
// Adding self link employee collection resource
Link selfLink =
linkTo(methodOn(EmployeeController.class).getAllEmployees())
.withSelfRel();
// Add link to collection resource
employeesList.add(selfLink);
return employeesList;
}
@GetMapping("/employees/{id}")
public ResponseEntity<EmployeeVO> getEmployeeById(
@PathVariable("id") int id) {
if (id <= 3) {
EmployeeVO employee = EmployeeDB.getEmployeeList().get(id - 1);
// Self link
Link selfLink = linkTo(EmployeeController.class)
.slash(employee.getEmployeeId()).withSelfRel();
// Method link
Link reportLink = linkTo(methodOn(EmployeeController.class)
.getReportByEmployeeById(employee.getEmployeeId()))
.withRel("report");
employee.add(selfLink);
employee.add(reportLink);
return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}
return new ResponseEntity<EmployeeVO>(HttpStatus.NOT_FOUND);
}
@GetMapping("/employees/{id}/report")
public ResponseEntity<EmployeeReport> getReportByEmployeeById(
@PathVariable("id") int id) {
// Do some operation and return report
return null;
}
}
3.4. API Responses
/employees
{
"employees": [
{
"employeeId": 1,
"firstName": "Lokesh",
"lastName": "Gupta",
"email": "howtodoinjava@gmail.com",
"_links": {
"self": {
"href": "http://localhost:8080/1"
},
"employee-report": {
"href": "http://localhost:8080/employees/1/report"
}
}
},
{
"employeeId": 2,
"firstName": "Amit",
"lastName": "Singhal",
"email": "asinghal@yahoo.com",
"_links": {
"self": {
"href": "http://localhost:8080/2"
},
"employee-report": {
"href": "http://localhost:8080/employees/2/report"
}
}
},
{
"employeeId": 3,
"firstName": "Kirti",
"lastName": "Mishra",
"email": "kmishra@gmail.com",
"_links": {
"self": {
"href": "http://localhost:8080/3"
},
"employee-report": {
"href": "http://localhost:8080/employees/3/report"
}
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/employees"
}
}
}
/employees/{id}
{
"employeeId": 1,
"firstName": "Lokesh",
"lastName": "Gupta",
"email": "howtodoinjava@gmail.com",
"_links": {
"self": {
"href": "http://localhost:8080/1"
},
"report": {
"href": "http://localhost:8080/employees/1/report"
}
}
}
4. Conclusion
As we saw in the demo that adding HATEOAS links using spring boot hateoas module is very much easy and requires very less time and effort. In return, it increases the discoverability and usefulness of APIs by many folds.
Happy Learning !!
Leave a Reply