skip to content

What is CollectionModel<T> and how does it differ from returning a plain list? What does its HAL output look like?

level: middleimportance: should knowfreq 55%

answer

  1. wraps many + collection-level links
  2. items under _embedded, links under _links
  3. rel name derived from element type / @Relation
  4. T often EntityModel<X>
  5. step up to PagedModel for paging

basics

~10 s

CollectionModel<T> wraps a collection of items so the whole collection can carry its own hypermedia links (like a self link). In HAL the items go under _embedded and collection-level links under _links.

solid answer

~40 s

`CollectionModel<T>` is a `RepresentationModel` that holds many items plus collection-level links. A plain `List<Order>` can only serialize the items — it has nowhere to attach links describing the collection (self, next page, create action). `CollectionModel.of(items, linkTo(...).withSelfRel())` fixes that. `T` is usually `EntityModel<Order>` so each element also carries its own links, but it can be a plain type too. In HAL (`application/hal+json`), the elements render under an `_embedded` object keyed by a relation name (derived from the element type, e.g. `orderList`), and the collection's own links render under a top-level `_links`. You typically build it in a `RepresentationModelAssembler` via `toCollectionModel(...)`. It is the right return type for list endpoints that need hypermedia; for paged data you step up to `PagedModel`.

code

java · 17 lines
java
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;

@GetMapping("/orders")
public CollectionModel<EntityModel<Order>> all() {
    List<EntityModel<Order>> items = repository.findAll().stream()
        .map(o -> EntityModel.of(o,
            linkTo(methodOn(OrderController.class).one(o.getId())).withSelfRel()))
        .toList();
    return CollectionModel.of(items,
        linkTo(methodOn(OrderController.class).all()).withSelfRel());
}

// Control the _embedded key on the element type:
// @Relation(collectionRelation = "orders")
// public class Order { ... }

go deeper

for a junior

Know it wraps many items so the collection can carry links; items go under _embedded.

for a middle

Explain the type-derived rel, @Relation, and per-item vs collection-level links.

for a senior

Drive it from an assembler and reason about empty-collection and rel-stability gotchas.

for a principal

Treat the _embedded rel as a client contract and govern renames/versioning accordingly.

**Problem it solves.** A REST list endpoint often needs links that describe the *collection itself*, not just each item: a `self` link to the list, a `create` action, or pagination links. A raw `List<Order>` (or `List<EntityModel<Order>>`) is just a JSON array — an array has no place to hang links. `CollectionModel<T>` wraps the collection into an object so it can carry a `_links` block. **Type.** `CollectionModel<T> extends RepresentationModel<CollectionModel<T>>`. It holds an `Iterable<T>` of content plus links. The element type `T` is commonly `EntityModel<Order>` (so each item has its own links) but may be a plain domain type or DTO when per-item links aren't needed. **Factories.** - `CollectionModel.of(Iterable<T> content)` - `CollectionModel.of(Iterable<T> content, Link... links)` - `CollectionModel.empty()` for an empty collection (still a valid resource). **HAL serialization.** With `application/hal+json`: - The items are placed under `_embedded`, keyed by a *relation name*. By default the rel is derived from the element type name (e.g. a collection of `Order` → `orderList`). You can control it with `@Relation(collectionRelation = "orders")` on the element type, or via a `LinkRelationProvider`. - Collection-level links go under a top-level `_links`. Example: ``` { "_embedded": { "orderList": [ {"id":1, "_links":{...}}, ... ] }, "_links": { "self": {"href":"/orders"} } } ``` **Empty-collection gotcha.** With HAL, an empty `CollectionModel` may omit `_embedded` entirely (because there is no element type to derive the rel from), so clients should not assume the key is always present. Some teams use `CollectionModel.empty()` plus explicit rel via `withFallbackType`/typed empties to keep the key stable. **Building it.** The idiomatic path is a `RepresentationModelAssembler<Order, EntityModel<Order>>` whose default `toCollectionModel(Iterable<Order>)` maps each entity through `toModel` and wraps the result. You then `.add(...)` a self link. **When to use.** - Unpaged list endpoints needing hypermedia → `CollectionModel`. - Paged endpoints (page size / total / navigation) → `PagedModel` (a subclass). - Single item → `EntityModel`. **Gotchas.** - The `_embedded` rel name is type-derived; renaming the element class silently changes the client-visible key unless you pin it with `@Relation`. - `CollectionModel` gives collection-level links but does NOT add pagination metadata — that's `PagedModel`. - Returning `List<EntityModel<Order>>` gives per-item links but still no collection self link; wrap in `CollectionModel` to get both.

  • Why is the `_embedded` key sometimes `orderList` and how do you make it `orders`?
    By default the collection relation is derived from the element type name via the LinkRelationProvider (e.g. `orderList`). Annotate the element type with `@Relation(collectionRelation = "orders")` to pin it.
  • What breaks if you return `List<EntityModel<Order>>` instead of `CollectionModel`?
    You get per-item links but a bare JSON array — there is nowhere to attach collection-level links like the list's own `self` or pagination links.

saying these in an interview costs you the question

  • Claiming CollectionModel adds pagination metadata (that's PagedModel)
  • Assuming the `_embedded` key is always the same regardless of element type name
  • Thinking a plain List can carry collection-level links

context