spring-hateoas

v2026.09.24

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

GitHub
Install command
npx skhub add claude-dev-suite/spring-hateoas
Markdown
SKILL.md

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

DoDon't
Use ModelAssembler patternBuild links inline everywhere
Include self links alwaysOmit navigation links
Use standard IANA relationsInvent new relation names
Add conditional links for actionsShow 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-PatternProblemSolution
Building links inlineDuplicated code everywhereUse RepresentationModelAssembler
Missing self linksClients can't identify resourcesAlways add withSelfRel()
Hardcoded URLsBreaks on deploymentUse linkTo/methodOn builders
Inventing relationsNon-standard, hard to understandUse IANA link relations

Quick Troubleshooting

ProblemDiagnosticFix
Links not serializedCheck Accept headerEnsure HAL media type
NullPointerException in linkToCheck controller methodVerify method signature matches
Wrong base URLCheck proxy configConfigure server.forward-headers-strategy
Pagination links missingCheck assemblerUse 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

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/backend-frameworks/spring-hateoas

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1