# spring-hateoas
> Spring HATEOAS for building hypermedia-driven RESTful APIs.
Covers EntityModel, CollectionModel, RepresentationModelAssembler, HAL, and affordances.
USE WHEN: user mentions "HATEOAS", "hypermedia", "HAL", "EntityModel",
"CollectionModel", "RepresentationModelAssembler", "Richardson Maturity Level 3"
DO NOT USE FOR: simple REST APIs without hypermedia - use plain Spring MVC,
GraphQL - use `spring-graphql` skill
- Author: mariepellegrino89
- Repository: claude-dev-suite/claude-dev-suite
- Version: 20260206202537
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-06
- Source: https://github.com/claude-dev-suite/claude-dev-suite
- Web: https://mule.run/skillshub/@@claude-dev-suite/claude-dev-suite~spring-hateoas:20260206202537
---
---
name: spring-hateoas
description: |
Spring HATEOAS for building hypermedia-driven RESTful APIs.
Covers EntityModel, CollectionModel, RepresentationModelAssembler, HAL, and affordances.
USE WHEN: user mentions "HATEOAS", "hypermedia", "HAL", "EntityModel",
"CollectionModel", "RepresentationModelAssembler", "Richardson Maturity Level 3"
DO NOT USE FOR: simple REST APIs without hypermedia - use plain Spring MVC,
GraphQL - use `spring-graphql` skill
allowed-tools: Read, Grep, Glob, Write, Edit
---
# Spring HATEOAS - Quick Reference
> **Full Reference**: See [advanced.md](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
```xml
org.springframework.boot
spring-boot-starter-hateoas
```
## 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
```java
@GetMapping("/users/{id}")
public EntityModel 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)
```json
{
"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
```java
@GetMapping("/users")
public CollectionModel> getAllUsers() {
List users = userService.findAll();
List> 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
```java
@Component
public class UserModelAssembler implements RepresentationModelAssembler> {
@Override
public EntityModel 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 getUser(@PathVariable Long id) {
return assembler.toModel(userService.findById(id));
}
@GetMapping
public CollectionModel> getAllUsers() {
return assembler.toCollectionModel(userService.findAll());
}
}
```
## Pagination Support
```java
@GetMapping("/users")
public PagedModel> getAllUsers(
@PageableDefault(size = 20) Pageable pageable,
PagedResourcesAssembler pagedAssembler) {
Page 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
- [ ] ModelAssemblers for all resources
- [ ] Self links on every resource
- [ ] Collection links from items
- [ ] Pagination with navigation links
- [ ] Conditional action links
- [ ] IANA link relations where possible
## Reference Documentation
- [Spring HATEOAS Reference](https://docs.spring.io/spring-hateoas/docs/current/reference/html/)