Spring HATEOAS - Quick Reference
Full Reference: See advanced.md for affordances (HAL-FORMS), embedded resources, link relations, media types, and testing patterns.
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-hateoas for comprehensive documentation.
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
Core Concepts
Richardson Maturity Model:
Level 0: Plain Old XML/JSON (single endpoint)
Level 1: Resources (multiple endpoints)
Level 2: HTTP Verbs (GET, POST, PUT, DELETE)
Level 3: Hypermedia Controls (HATEOAS) ← This!
EntityModel Wrapper
@GetMapping("/users/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
linkTo(methodOn(UserController.class).getAllUsers()).withRel("users"),
linkTo(methodOn(OrderController.class).getOrdersByUser(id)).withRel("orders")
);
}
Response Format (HAL)
{
"id": 1,
"name": "John Doe",
"_links": {
"self": { "href": "http://localhost:8080/api/users/1" },
"users": { "href": "http://localhost:8080/api/users" },
"orders": { "href": "http://localhost:8080/api/users/1/orders" }
}
}
CollectionModel
@GetMapping("/users")
public CollectionModel<EntityModel<User>> getAllUsers() {
List<User> users = userService.findAll();
List<EntityModel<User>> userModels = users.stream()
.map(user -> EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel()
))
.toList();
return CollectionModel.of(userModels,
linkTo(methodOn(UserController.class).getAllUsers()).withSelfRel()
);
}
RepresentationModelAssembler
@Component
public class UserModelAssembler implements RepresentationModelAssembler<User, EntityModel<User>> {
@Override
public EntityModel<User> toModel(User user) {
return EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel(),
linkTo(methodOn(UserController.class).getAllUsers()).withRel("users")
);
}
}
// Usage
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserModelAssembler assembler;
@GetMapping("/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
return assembler.toModel(userService.findById(id));
}
@GetMapping
public CollectionModel<EntityModel<User>> getAllUsers() {
return assembler.toCollectionModel(userService.findAll());
}
}
Pagination Support
@GetMapping("/users")
public PagedModel<EntityModel<User>> getAllUsers(
@PageableDefault(size = 20) Pageable pageable,
PagedResourcesAssembler<User> pagedAssembler) {
Page<User> users = userService.findAll(pageable);
return pagedAssembler.toModel(users, userModelAssembler);
}
Best Practices
| Do |
Don't |
| Use ModelAssembler pattern |
Build links inline everywhere |
| Include self links always |
Omit navigation links |
| Use standard IANA relations |
Invent new relation names |
| Add conditional links for actions |
Show all links regardless of state |
When NOT to Use This Skill
- Simple REST APIs - If clients don't need hypermedia navigation
- GraphQL APIs - Use
spring-graphql instead
- Internal microservices - Often unnecessary overhead
Anti-Patterns
| Anti-Pattern |
Problem |
Solution |
| Building links inline |
Duplicated code everywhere |
Use RepresentationModelAssembler |
| Missing self links |
Clients can't identify resources |
Always add withSelfRel() |
| Hardcoded URLs |
Breaks on deployment |
Use linkTo/methodOn builders |
| Inventing relations |
Non-standard, hard to understand |
Use IANA link relations |
Quick Troubleshooting
| Problem |
Diagnostic |
Fix |
| Links not serialized |
Check Accept header |
Ensure HAL media type |
| NullPointerException in linkTo |
Check controller method |
Verify method signature matches |
| Wrong base URL |
Check proxy config |
Configure server.forward-headers-strategy |
| Pagination links missing |
Check assembler |
Use PagedResourcesAssembler |
Production Checklist
Reference Documentation