Skip to content
cccskills
FreePublished on GitHub

hateoas

Use when adding hypermedia links to REST responses, building self-describing APIs, or implementing Spring HATEOAS. Use when you see EntityModel, CollectionModel, or RepresentationModel in the project.

The English summary is being prepared. Showing the original description.

See how to install

Files included (4)

  • SKILL.md5.5 KB
  • examples/bad-hateoas-controller.java1.6 KB
  • examples/good-hateoas-controller.java2.0 KB
  • templates/OrderModelAssembler.java1.9 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Spring HATEOAS

Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

When to Add Links

  • self — always, on every resource response
  • collection — link back to the list endpoint
  • related resources — when a client commonly needs to navigate to them
  • actions — links to state transitions (e.g., cancel, ship) when valid for current state

Resource Model

public class OrderModel extends RepresentationModel<OrderModel> {
    private final UUID id;
    private final String status;
    private final String customerEmail;
    private final Instant createdAt;

    // Static factory with links
    public static OrderModel from(Order order) {
        OrderModel model = new OrderModel(
            order.getId(), order.getStatus().name(),
            order.getCustomerEmail(), order.getCreatedAt()
        );

        // Self link — always
        model.add(linkTo(methodOn(OrderController.class).getById(order.getId())).withSelfRel());

        // Collection link
        model.add(linkTo(methodOn(OrderController.class).list(null)).withRel("orders"));

        // Conditional action links based on state
        if (order.getStatus() == OrderStatus.PENDING) {
            model.add(linkTo(methodOn(OrderController.class)
                .cancelOrder(order.getId())).withRel("cancel"));
        }
        if (order.getStatus() == OrderStatus.PROCESSING) {
            model.add(linkTo(methodOn(OrderController.class)
                .shipOrder(order.getId())).withRel("ship"));
        }

        return model;
    }
}

Controller

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    @GetMapping("/{id}")
    public ResponseEntity<OrderModel> getById(@PathVariable UUID id) {
        Order order = orderService.findById(id);
        return ResponseEntity.ok(OrderModel.from(order));
    }

    @GetMapping
    public ResponseEntity<CollectionModel<OrderModel>> list(Pageable pageable) {
        Page<Order> orders = orderService.findAll(pageable);

        List<OrderModel> models = orders.getContent().stream()
            .map(OrderModel::from)
            .toList();

        CollectionModel<OrderModel> collection = CollectionModel.of(models,
            linkTo(methodOn(OrderController.class).list(pageable)).withSelfRel()
        );

        // Pagination links
        if (orders.hasNext()) {
            collection.add(linkTo(methodOn(OrderController.class)
                .list(pageable.next())).withRel(IanaLinkRelations.NEXT));
        }
        if (orders.hasPrevious()) {
            collection.add(linkTo(methodOn(OrderController.class)
                .list(pageable.previousOrFirst())).withRel(IanaLinkRelations.PREV));
        }

        return ResponseEntity.ok(collection);
    }
}

Response Shape

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "PENDING",
  "customerEmail": "user@example.com",
  "_links": {
    "self": { "href": "http://api.example.com/api/v1/orders/550e8400" },
    "orders": { "href": "http://api.example.com/api/v1/orders" },
    "cancel": { "href": "http://api.example.com/api/v1/orders/550e8400/cancel" }
  }
}

RepresentationModelAssembler Pattern

  • Spring's recommended way to build HATEOAS models from entities
  • Implements RepresentationModelAssembler<Entity, Model> — reusable across controllers
  • Inject the assembler into controllers instead of calling Model.from() directly
@Component
public class OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<OrderResponse>> {

    @Override
    public EntityModel<OrderResponse> toModel(Order order) {
        EntityModel<OrderResponse> model = EntityModel.of(OrderResponse.from(order),
            linkTo(methodOn(OrderController.class).getById(order.getId())).withSelfRel(),
            linkTo(methodOn(OrderController.class).list(null)).withRel("orders"));

        if (order.getStatus() == OrderStatus.PENDING) {
            model.add(linkTo(methodOn(OrderController.class)
                .cancelOrder(order.getId())).withRel("cancel"));
        }
        return model;
    }
}

PagedModel for Paginated Collections

  • Use PagedResourcesAssembler for automatic pagination links (first, prev, next, last)
  • Inject PagedResourcesAssembler<Order> into controllers — Spring creates it automatically
@GetMapping
public ResponseEntity<PagedModel<EntityModel<OrderResponse>>> list(
        Pageable pageable, PagedResourcesAssembler<Order> pagedAssembler) {
    Page<Order> orders = orderService.findAll(pageable);
    PagedModel<EntityModel<OrderResponse>> pagedModel =
        pagedAssembler.toModel(orders, orderModelAssembler);
    return ResponseEntity.ok(pagedModel);
}

Gotchas

  • Agent adds all links regardless of state — only add action links when the action is valid
  • Agent hardcodes URLs in links — always use linkTo(methodOn(...)) for type-safe links
  • Agent returns plain DTO — wrap in EntityModel.of(dto, links...) or extend RepresentationModel
  • Agent puts link logic in controller — extract to RepresentationModelAssembler
  • Agent manually builds pagination links — use PagedResourcesAssembler instead
  • Agent forgets self link — every resource must have a self link

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when adding monitoring, metrics, logging, or tracing to Spring AI or LLM integration code. Covers token tracking, latency measurement, cost estimation, and prompt/response logging. Use when user mentions AI monitoring, token costs, or LLM observability.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

Provides expert knowledge for building modern native Android apps using Kotlin, Jetpack Compose, Material Design 3, MVI architecture, Hilt dependency injection, and Media3/ExoPlayer.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

Design, implement, review, and audit Android Jetpack Compose UI/UX for phone interfaces. Use when a request concerns concrete Compose layout, components, interaction, accessibility, or visual behavior. Do not activate for framework or dependency presence alone; non-UI Android work is excluded.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

Provides expert knowledge for designing, implementing, and managing REST API versioning strategies with deprecation workflows.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

astro

Free

Provides expert knowledge for building Astro 5+ sites with Islands Architecture, Content Collections, TypeScript, and performance-first rendering strategies.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

Audits token storage mechanisms to prevent XSS-based JWT theft.

The English summary is being prepared. Showing the original description.

lgzarturo/springboot-course3Updated Oct 17, 2026

See all skills by lgzarturo

Report a problem with this skill