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.

  1. We generally get the list of Users in one page with Get User Info and Delete User link per User.
  2. By clicking on Get User Info link, we get info of particular user.
  3. 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.

  1. Get User List API – we will give self-link and per user Get User Info and Delete User link,
  2. Get User Info API – we will give a self-link,
  3. 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

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 uses  ControllerLinkBuilder 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 in href 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 call  withRel() 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 withTitlewithHref, 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 @ControllerAdvice and  @ExceptionHandler .

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)

Was this post helpful?
Let us know, if you liked the post. Only in this way, we can improve us.
Yes
No

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.

Leave a Reply

Your email address will not be published. Required fields are marked *