

Table of Contents
1. Overview
HATEOAS
(Hypermedia as the Engine of Application State) is an important component of the REST application architecture. Here, Hypermedia represents Hyperlinks and we can say APIs driven based on Hyperlinks. REST client can get further navigational information about the system from hyperlinks embedded in REST response. We can get API endpoint links in response and use that links to perform other related REST operations. There is no more documentation needed, only some basic API knowledge needed to use it.
2. Example
First of all, take a UI example before implementing HATEOAS APIs. There are scenarios in which we are accessing User List, User Info and Delete User functionalities from the UI side.
- We generally get the list of Users in one page with Get User Info and Delete User link per User.
- By clicking on Get User Info link, we get info of particular user.
- By clicking on Delete user link, the particular user gets deleted and redirects to User List.
To implement this scenario, in HATEOAS REST API with Spring Boot, we will create 3 APIs Get User List, Get User Info, Delete User.
- Get User List API – we will give self-link and per user Get User Info and Delete User link,
- Get User Info API – we will give a self-link,
- Delete User API – we will give a Get User List Link.
In this example, we have used Spring Data JPA and in-memory H2 database for data related operation purpose. As keeping HATEOAS centre topic, explanation related to JPA configuration has been omitted. You can find it in the source code attached.
2.1 Project Structure

Project Structure for Spring Boot with HATEOAS
2.2 POM file configuration
We need to include the dependency of spring-boot-starter-hateoas
for HATEOAS support in pom.xml or gradle file. Other dependencies like web, data-jpa and h2 have been added for additional support for example.
<?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.javadeveloperzone</groupId> <artifactId>hateoas</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-hateoas-example</name> <description>Demo project for HATEOAS with Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> </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-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-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-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.3 Model Object
ResourceSupport
inheritance used for resource representation with links. It gives support for an entity to Link related methods like add, getLink, etc. So, we can set links to the resource without adding any new fields.
package com.javadeveloperzone.hateoas.model; import org.springframework.hateoas.ResourceSupport; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User extends ResourceSupport { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; private String firstName; private String lastName; public User() { } public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } 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; } }
2.4 Controller
In this Controller file, three API endpoints Get User List, Get User Info and Delete User has been defined.
package com.javadeveloperzone.hateoas.controller; import com.javadeveloperzone.hateoas.exception.UserNotFoundException; import com.javadeveloperzone.hateoas.model.User; import com.javadeveloperzone.hateoas.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.hateoas.*; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @RestController public class UserController { @Autowired UserService userService; @GetMapping(value = "/user/{id}", produces = {"application/hal+json"}) public Resource<User> getUser(@PathVariable(value = "id", required = true) Long id) { User user = userService.getUserById(id).orElse(null); if (user == null) { throw new UserNotFoundException("User not found with id - " + id); } user.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel().withType("GET")); return new Resource<User>(user); } @GetMapping(value = "/users", produces = {"application/hal+json"}) public Resources<User> getUsers() { List<User> users = userService.getAllUsers(); for (User user : users) { Long userId = user.getUserId(); user.add(linkTo(methodOn(UserController.class).getUser(userId)).withRel("get_user").withType("GET")); user.add(linkTo(methodOn(UserController.class).deleteUser(userId)).withRel("delete_user").withType("DELETE")); } Link link = linkTo(methodOn(UserController.class).getUsers()).withSelfRel().withType("GET"); return new Resources<User>(users, link); } @DeleteMapping(value = "/user/{id}", produces = {"application/hal+json"}) public Resource<User> deleteUser(@PathVariable(value = "id", required = true) Long id) { Optional<User> user = userService.getUserById(id); if (!user.isPresent()) { throw new UserNotFoundException("User not found with id - " + id); } userService.deleteUserById(id); User userNull = new User(); userNull.add(linkTo(methodOn(UserController.class).getUsers()).withRel("get_users").withType("GET")); return new Resource<User>(userNull); } }
- Here, we have used Resource/Resources object to complying with the HAL return type.
- If you want to create simple static Link, you can create using Link constructor as per below:
Link link = new Link("http://localhost:8080/users");
linkto()
method: The linkTo method usesControllerLinkBuilder
that allows creating links by pointing to controller class and method in it. By combining root mapping url, method’s url and other parameters, it builds final url. Final URL will display inhref
key’s value in response.methodOn()
method: If you want to add the link of any API endpoint from Controller’s method then just need to call methodOn in linkTo method with Controller name class. Here, we need to call the method of API endpoint with proper params. So, it creates the link from that.withSelfRel()
method: It is used for define relationship with the current API. Here, it’s a self-referencing hyperlink means it ’s link of itself. If you want to define other relation links then, need to callwithRel()
with relation name param in that.withType()
method: It is used to define the type of link like GET, POST, etc. We can set the request type in method argument as a string, so a client can get knowledge about request type.slash()
method: This method adds the parameter value as the path variable of the link.- There are more methods using that we add more information related to link in response like
withTitle
,withHref
,withHrefLang
,withMedia
,withDeprecation
, etc.
2.5 Exception Handling
To handle the custom exception, we need to create a Class extending RuntimeException
with default constructor calling super. To specify response status for exception response, we used @ResponseStatus
annotation with response status. This is the simplest way to handle an exception. You can handle exception detailed way using @
and
package com.javadeveloperzone.hateoas.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.NOT_FOUND) public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String exception) { super(exception); } }
2.6 Output
Step 1: First we call Get User List API. We will get other links from its response. Go to REST client and call http://localhost:8080/users
with GET request type. You will get below outputs.
Note: When we use Resources
return type, at that time by default “_embedded
” key will come in response. If you want to remove “_embedded
” from response then you need to below configuration:
In application.properties
add property spring.hateoas.use-hal-as-default-json-media-type=false
and in Spring Boot Application file add @EnableAutoConfiguration(exclude = HypermediaAutoConfiguration.class)
annotation at class level.
{ "_embedded": { "userList": [ { "userId": 1, "firstName": "Tom", "lastName": "Cruise", "_links": { "get_user": { "href": "http://localhost:8080/user/1", "type": "GET" }, "delete_user": { "href": "http://localhost:8080/user/1", "type": "DELETE" } } }, { "userId": 2, "firstName": "Vin", "lastName": "Diesel", "_links": { "get_user": { "href": "http://localhost:8080/user/2", "type": "GET" }, "delete_user": { "href": "http://localhost:8080/user/2", "type": "DELETE" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/users", "type": "GET" } } }
Step 2: From the above output, we can get links for Get User Info and Delete User. Call Get User Info API from the link http://localhost:8080/user/1
with GET type. You will get below output.
{ "userId": 1, "firstName": "Tom", "lastName": "Cruise", "_links": { "self": { "href": "http://localhost:8080/user/1", "type": "GET" } } }
Step 3: Call Delete User API from the link http://localhost:8080/user/1
with DELETE type. You will get below output.
{ "userId": null, "firstName": null, "lastName": null, "_links": { "get_users": { "href": "http://localhost:8080/users", "type": "GET" } } }
Step 4: We got Get Users List API link in response that is like UI operation when we delete user successfully, we redirect to User list page. To test exception response, let’s call Delete User API with not existing id. Call Delete User API from the link http://localhost:8080/user/10
with DELETE type. You will get below output.
{ "timestamp": "2018-08-12T10:10:35.276+0000", "status": 404, "error": "Not Found", "message": "User not found with id - 10", "path": "/user/10" }
3. Conclusion
In this tutorial, we learned that how to implement basic HATEOAS REST APIs with Spring Boot. We have seen that REST client can get navigational links of other functionalities with help of hypermedia links in the response of API. Therefore, no more documentation related to APIs needed and server can change URL scheme without breaking the client.
4. References
5. Source code
spring-boot-hateoas-example (63 KB)
2 comments. Leave new
Its really new to me but this simple explanation is very useful
Thank you parth.
Here are more spring boot article, Hope it will be helpful to you.