Spring HATEOAS - Quick Reference
SkillAI & modelsSpring HATEOAS for building hypermedia-driven RESTful APIs. Covers EntityModel, CollectionModel, RepresentationModelAssembler, HAL, and affordances.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Spring HATEOAS - Quick Reference skill
What this skill tells your AI
The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/spring-hateoas/SKILL.md and read by ahel’s review.
Full Reference: See advanced.md for affordances (HAL-FORMS), embedded resources, link relations, media types, and testing patterns.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:spring-hateoasfor 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-graphqlinstead - 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
Signals
- GitHub stars
- 33
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
spring-hateoas- Source
- github.com/claude-dev-suite/claude-dev-suite