Die Oberfläche ist lokalisiert; Skill-Beschreibungen und -Dokumente werden per KI automatisch in Ihre Sprache übersetzt. Code, Befehle, Pfade und Fachbegriffe bleiben im Original, maßgeblich ist das Dokument des Autors.
Skills entdecken
Entwicklung · SKILL.md

spring-hateoas

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 - us…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill-Dokumentation

GitHub

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 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

Gespeicherte Skills