Close

Spring HATEOAS - Example of Linking multiple resources

[Last Updated: Jul 12, 2018]

Following example shows how to link multiple resources using Spring HATEOAS API.

Example

Domain classes

public class Employee {
    private long employeeId;
    private String name;
    private Dept dept;
    .............
}
public class Dept {
    private long deptId;
    private String name;
    .............
}
public class Task {
    private long taskId;
    private String name;
    private Employee employee;
    .............
}

Controllers

We are creating following resources:

  • /employees
  • /employees/{employeeId}
  • /employees/{employeeId}/tasks
  • /tasks
  • /tasks/{taskId}
  • /depts
  • /depts/{deptId}
package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
@RequestMapping("/employees")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @GetMapping
    public Resources<Resource<Employee>> getEmployees() {
        List<Employee> employeeList = employeeService.getAllEmployees();
        List<Resource<Employee>> employeeResources =
                employeeList.stream()
                            .map(EmployeeController::createEmployeeResource)
                            .collect(Collectors.toList());
        Link selfRel = linkTo(methodOn(EmployeeController.class)
                .getEmployees()).withSelfRel();
        return new Resources<>(employeeResources, selfRel);
    }

    @GetMapping("/{employeeId}")
    public Resource<Employee> getEmployeeById(@PathVariable long employeeId) {
        Employee employee = employeeService.getEmployeeById(employeeId);
        Link selfLink = linkTo(methodOn(EmployeeController.class)
                .getEmployeeById(employeeId)).withSelfRel();
        return new Resource<>(employee, selfLink, createTasksLink(employeeId),
                createDeptLink(employee.getDept().getDeptId()));
    }

    private static Resource<Employee> createEmployeeResource(Employee e) {
        Link employeeLink = linkTo(methodOn(EmployeeController.class)
                .getEmployeeById(e.getEmployeeId())).withSelfRel();
        return new Resource<>(e, employeeLink);
    }

    private static Link createTasksLink(long employeeId) {
        return linkTo(methodOn(TaskController.class)
                .getEmployeeTasks(employeeId)).withRel("tasks");
    }

    private Link createDeptLink(long deptId) {
        return linkTo(methodOn(DeptController.class)
                .getDept(deptId)).withRel("dept");
    }
}
@RestController
public class TaskController {
    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees/{employeeId}/tasks")
    public Resources<Resource<Task>> getEmployeeTasks(@PathVariable long employeeId) {
        List<Task> tasks = employeeService.getTasksByEmployeeId(employeeId);
        List<Resource<Task>> taskResources = tasks.stream()
                                                  .map(TaskController::createTaskResource)
                                                  .collect(Collectors.toList());
        return new Resources<>(taskResources);
    }

    @GetMapping("/tasks/{taskId}")
    public Resource<Task> getTask(@PathVariable long taskId) {
        Task task = employeeService.getTaskByTaskId(taskId);
        Link selfRel = linkTo(methodOn(TaskController.class).getTask(taskId)).withSelfRel();
        Resource<Task> taskResource = new Resource<>(task, selfRel);
        taskResource.add(createEmployeeLink(task.getEmployee().getEmployeeId()));
        return taskResource;
    }

    @GetMapping("/tasks")
    public Resources<Resource<Task>> getAllTasks() {
        List<Task> tasks = employeeService.getAllTasks();
        Link selfRel = linkTo(methodOn(TaskController.class).getAllTasks()).withSelfRel();
        List<Resource<Task>> taskResources = tasks.stream()
                                                  .map(TaskController::createTaskResource)
                                                  .collect(Collectors.toList());
        return new Resources<>(taskResources, selfRel);
    }

    private static Resource<Task> createTaskResource(Task t) {
        Link selfRel = linkTo(methodOn(TaskController.class).getTask(t.getTaskId())).withSelfRel();
        return new Resource<>(t, selfRel,
                createEmployeeLink(t.getEmployee().getEmployeeId()));
    }

    static Link createEmployeeLink(long employeeId) {
        return linkTo(methodOn(EmployeeController.class).getEmployeeById(employeeId))
                .withRel("employee");
    }
}
@RestController
public class DeptController {
    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/depts/{deptId}")
    public Resource<Dept> getDept(@PathVariable long deptId) {
        Dept dept = employeeService.getDeptByDeptId(deptId);
        Resource<Dept> deptResource = createDeptResource(dept);
        List<Employee> employees = employeeService.getEmployeesByDept(deptId);
        employees.stream()
                 .forEach(e -> deptResource.add(TaskController.createEmployeeLink(e.getEmployeeId())));
        deptResource.add(linkTo(methodOn(DeptController.class).getAllDepts()).withRel("depts"));
        return deptResource;
    }

    @GetMapping("/depts")
    public Resources<Resource<Dept>> getAllDepts() {
        List<Dept> depts = employeeService.getAllDept();
        Link selfRel = linkTo(methodOn(DeptController.class).getAllDepts()).withSelfRel();
        List<Resource<Dept>> deptResources = depts.stream()
                                                  .map(d -> createDeptResource(d))
                                                  .collect(Collectors.toList());
        return new Resources<>(deptResources, selfRel);
    }

    private static Resource<Dept> createDeptResource(Dept dept) {
        Link selfRel = linkTo(methodOn(DeptController.class).getDept(dept.getDeptId())).withSelfRel();
        return new Resource<>(dept, selfRel);
    }
}

Running

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run-war

Output

We are going to use

Let's get all employees first:

$ curl -s http://localhost:8080/employees | jq
{
"_embedded" : {
"employeeList" : [ {
"employeeId" : 1,
"name" : "Lara",
"dept" : {
"deptId" : 1,
"name" : "QA"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/1"
}
}
}, {
"employeeId" : 3,
"name" : "Tina",
"dept" : {
"deptId" : 2,
"name" : "IT"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/3"
}
}
}, {
"employeeId" : 2,
"name" : "Tom",
"dept" : {
"deptId" : 1,
"name" : "QA"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/2"
}
}
}, {
"employeeId" : 4,
"name" : "John",
"dept" : {
"deptId" : 2,
"name" : "IT"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/4"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees"
}
}
}

Employee with id 4:

$ curl -s http://localhost:8080/employees/4 | jq
{
"employeeId" : 4,
"name" : "John",
"dept" : {
"deptId" : 2,
"name" : "IT"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/4"
},
"tasks" : {
"href" : "http://localhost:8080/employees/4/tasks"
},
"dept" : {
"href" : "http://localhost:8080/depts/2"
}
}
}

Now let's get above employee's dept via http://localhost:8080/depts/2

$ curl -s http://localhost:8080/depts/2 | jq
{
"deptId" : 2,
"name" : "IT",
"_links" : {
"self" : {
"href" : "http://localhost:8080/depts/2"
},
"employee" : [ {
"href" : "http://localhost:8080/employees/3"
}, {
"href" : "http://localhost:8080/employees/4"
} ],
"depts" : {
"href" : "http://localhost:8080/depts"
}
}
}

All tasks of employee with id = 4:

$ curl -s http://localhost:8080/employees/4/tasks | jq
{
"_embedded" : {
"taskList" : [ {
"taskId" : 1,
"name" : "Development",
"employee" : {
"employeeId" : 4,
"name" : "John",
"dept" : {
"deptId" : 2,
"name" : "IT"
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/tasks/1"
},
"employee" : {
"href" : "http://localhost:8080/employees/4"
}
}
}, {
"taskId" : 5,
"name" : "Deployment",
"employee" : {
"employeeId" : 4,
"name" : "John",
"dept" : {
"deptId" : 2,
"name" : "IT"
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/tasks/5"
},
"employee" : {
"href" : "http://localhost:8080/employees/4"
}
}
} ]
}
}
$ curl -s http://localhost:8080/tasks/1 | jq
{
"taskId" : 1,
"name" : "Development",
"employee" : {
"employeeId" : 4,
"name" : "John",
"dept" : {
"deptId" : 2,
"name" : "IT"
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/tasks/1"
},
"employee" : {
"href" : "http://localhost:8080/employees/4"
}
}
}

All depts:

$ curl -s http://localhost:8080/depts | jq
{
"_embedded" : {
"deptList" : [ {
"deptId" : 1,
"name" : "QA",
"_links" : {
"self" : {
"href" : "http://localhost:8080/depts/1"
}
}
}, {
"deptId" : 2,
"name" : "IT",
"_links" : {
"self" : {
"href" : "http://localhost:8080/depts/2"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/depts"
}
}
}

Example Project

Dependencies and Technologies Used:

  • spring-hateoas 0.24.0.RELEASE: Library to support implementing representations for hyper-text driven REST web services.
    Uses org.springframework:spring-web-mvc version 4.3.12.RELEASE
  • spring-plugin-core 1.2.0.RELEASE: Core plugin infrastructure.
  • jackson-databind 2.9.5: General data-binding functionality for Jackson: works on core streaming API.
  • javax.servlet-api 3.0.1 Java Servlet API
  • JDK 10
  • Maven 3.5.4

Spring HATEOAS - Linking multiple resources Select All Download
  • spring-hateoas-multiple-rel-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeController.java

    See Also