Spring HATEOAS (Hypermedia as the Engine of Application State) is a key module in the Spring ecosystem that simplifies creating RESTful APIs by including hypermedia links in responses. It encourages building APIs that are more self-descriptive and navigable, following REST principles closely.
Here’s a guide to get you started with Spring HATEOAS, along with examples.
Prerequisites
- Basic knowledge of Spring Boot
- JDK 11 or higher
- Maven or Gradle build tool
- An IDE like IntelliJ IDEA or Eclipse
Step 1: Create a Spring Boot Project
You can start by generating a new Spring Boot project from Spring Initializr with the following dependencies:
- Spring Web
- Spring HATEOAS
- Spring Data JPA (Optional: If you want to interact with a database)
Step 2: Add Dependencies
If you’re using Maven, add the following dependencies to your pom.xml
:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter HATEOAS -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA (Optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database (Optional) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
If you’re using Gradle, add the following dependencies to your build.gradle
:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Optional
runtimeOnly 'com.h2database:h2' // Optional
}
Step 3: Define a Simple Entity
Let’s assume you’re building a simple Employee
API.
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String role;
// Constructors, Getters, and Setters
public Employee() {}
public Employee(String name, String role) {
this.name = name;
this.role = role;
}
// Getters and Setters
}
Step 4: Create a Repository
You need a repository to manage your entity. Spring Data JPA simplifies this.
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
Step 5: Create a Resource Representation Model
To include hypermedia links in your response, you need a representation model.
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {
@Override
public EntityModel<Employee> toModel(Employee employee) {
return EntityModel.of(employee,
WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel(),
WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getAllEmployees()).withRel("employees"));
}
}
Step 6: Create a REST Controller
Next, create a REST controller that handles the HTTP requests.
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final EmployeeRepository repository;
private final EmployeeModelAssembler assembler;
public EmployeeController(EmployeeRepository repository, EmployeeModelAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
// GET all employees
@GetMapping
public CollectionModel<EntityModel<Employee>> getAllEmployees() {
List<EntityModel<Employee>> employees = repository.findAll().stream()
.map(assembler::toModel)
.collect(Collectors.toList());
return CollectionModel.of(employees,
WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getAllEmployees()).withSelfRel());
}
// GET employee by ID
@GetMapping("/{id}")
public EntityModel<Employee> getEmployeeById(@PathVariable Long id) {
Employee employee = repository.findById(id)
.orElseThrow(() -> new EmployeeNotFoundException(id));
return assembler.toModel(employee);
}
// POST new employee
@PostMapping
public ResponseEntity<?> createEmployee(@RequestBody Employee newEmployee) {
Employee savedEmployee = repository.save(newEmployee);
return ResponseEntity
.created(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getEmployeeById(savedEmployee.getId())).toUri())
.body(assembler.toModel(savedEmployee));
}
// PUT (update) employee
@PutMapping("/{id}")
public ResponseEntity<?> updateEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
Employee updatedEmployee = repository.findById(id)
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
})
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
return ResponseEntity.ok(assembler.toModel(updatedEmployee));
}
// DELETE employee
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
Step 7: Exception Handling
Handle exceptions gracefully, e.g., when an employee is not found.
@ResponseStatus(HttpStatus.NOT_FOUND)
public class EmployeeNotFoundException extends RuntimeException {
public EmployeeNotFoundException(Long id) {
super("Could not find employee " + id);
}
}
Step 8: Test Your Application
Run your Spring Boot application and test it using a tool like Postman or cURL.
For example, to retrieve all employees:
curl -X GET http://localhost:8080/employees
The response will include hypermedia links:
{
"_embedded": {
"employeeList": [
{
"id": 1,
"name": "John Doe",
"role": "Developer",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/employees"
}
}
}
Conclusion
Spring HATEOAS helps you build RESTful APIs that are self-descriptive and navigable, adhering to REST principles. By including hypermedia links in your responses, clients can discover available actions and resources without needing extensive documentation.
This example is a basic introduction to Spring HATEOAS, and you can expand it by exploring advanced features like custom links, pagination, and more complex resource assemblies.