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