skip to content

Design decision: when should you extend RepresentationModel<T> for your own type versus wrapping domain objects in EntityModel<T>/CollectionModel<T>? What are the trade-offs?

level: principalimportance: nice to knowfreq 30%

answer

  1. extend = named typed model, more coupling
  2. wrap = pure DTO, HATEOAS only at edge
  3. never make @Entity extend RepresentationModel
  4. assembler pattern works for both
  5. default wrap, extend for first-class contracts

basics

~20 s

Extend RepresentationModel when you own a dedicated, strongly-typed API model and want compile-time fields plus links in one class. Wrap with EntityModel/CollectionModel to keep domain/DTO classes free of HATEOAS and add links only at the edge.

solid answer

~50 s

Both give you a link-carrying response; the choice is about coupling and typing. Extending `RepresentationModel<MyModel>` bakes links into a purpose-built representation class — you get a concrete, strongly-typed model (nice for assemblers, tests, and clients reading a stable shape), and fluent `add(...)` returns your type via the self-referential generic. The cost: that class now depends on Spring HATEOAS and mixes payload with hypermedia concerns, and you write/maintain a dedicated type per resource. Wrapping with `EntityModel.of(domain, links)` / `CollectionModel.of(...)` keeps domain and DTO classes pure POJOs, adds links only in the controller/`RepresentationModelAssembler` layer, and is less boilerplate — at the cost of a generic wrapper type (`EntityModel<Order>`) rather than a named one. Common practice: wrap by default; extend when the representation is a first-class, long-lived API contract that benefits from an explicit named type. `RepresentationModelAssembler<T,D>` supports both.

code

java · 17 lines
java
// Option A: extend — strongly-typed, dedicated representation
class OrderModel extends RepresentationModel<OrderModel> {
    public long id; public BigDecimal total;
    // add(...) returns OrderModel thanks to the self-referential generic
}

// Option B: wrap — keep Order/OrderDto a plain POJO, links at the edge
EntityModel<Order> wrapped = EntityModel.of(order,
    linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel());

// Assembler supports either target type D:
class OrderAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> {
    public EntityModel<Order> toModel(Order o) {
        return EntityModel.of(o,
            linkTo(methodOn(OrderController.class).one(o.getId())).withSelfRel());
    }
}

go deeper

for a junior

Know both approaches exist; wrapping keeps DTOs plain.

for a middle

Articulate the coupling vs explicit-typing trade-off and avoid entities extending the base.

for a senior

Drive the decision per resource and wire assemblers for either target type.

for a principal

Set an org-wide convention balancing contract stability, module dependency hygiene, and boilerplate across many services.

**The two idioms.** 1. **Extend the base:** `class OrderModel extends RepresentationModel<OrderModel> { private long id; private BigDecimal total; ... }`. You return `OrderModel` directly and call `.add(link)` on it. The self-referential generic `RepresentationModel<OrderModel>` makes `add`/mutators return `OrderModel` for fluent chaining. 2. **Wrap the payload:** keep `Order` (entity) or `OrderDto` (plain DTO) untouched and return `EntityModel.of(order, links)` or `CollectionModel.of(items, links)`. **Axis 1 — Coupling.** Extending pulls `org.springframework.hateoas` into your representation type; if that type is shared (e.g. in a common module) it spreads the dependency. Wrapping isolates HATEOAS to the web edge, so domain/DTO modules stay framework-light. For hexagonal/clean-architecture codebases, wrapping aligns better with keeping the core dependency-free. **Axis 2 — Typing & clarity.** A named `OrderModel` is explicit: assemblers, unit tests, and client-code generators see a concrete type with known fields. `EntityModel<Order>` is generic — the type alone doesn't advertise which links exist. For a large public API where the representation is a first-class contract, an explicit model can be worth the boilerplate. **Axis 3 — Separation of concerns.** Extending mixes the data shape and the hypermedia affordances in one class; some teams consider that a smell, others find it convenient. Wrapping keeps the payload class about data only. **Axis 4 — Boilerplate & DTO reuse.** If you already maintain a DTO, wrapping reuses it directly. Extending means either making the DTO itself extend `RepresentationModel` (coupling it) or writing a parallel model class. **Where assemblers fit.** `RepresentationModelAssembler<T, D extends RepresentationModel<?>>` (with `toModel` and default `toCollectionModel`) works for either target `D` — `EntityModel<Order>` *or* a custom `OrderModel`. `RepresentationModelAssemblerSupport<T,D>` helps when extending (provides `createModelWithId`, etc.). So the assembler pattern is orthogonal to the choice. **Mapping matrix.** - Single item, pure DTO, isolate HATEOAS → `EntityModel<Order>`. - Single item, dedicated public contract / rich typed model → extend `RepresentationModel`. - Many items → `CollectionModel<...>` (or subclass) regardless; you rarely extend for collections. - Paged → `PagedModel<...>` via `PagedResourcesAssembler` (extending is uncommon here). **Gotchas.** - Making a JPA `@Entity` extend `RepresentationModel` couples persistence to the web layer — avoid; use a DTO or wrap. - If a shared model extends `RepresentationModel`, every consumer inherits the HATEOAS dependency and the `_links` serialization behavior. - The self-referential generic must be specified correctly (`extends RepresentationModel<OrderModel>`), or `add()` won't return your subtype. - Mixing both styles inconsistently across an API confuses clients; pick a convention. **Recommendation.** Default to wrapping (`EntityModel`/`CollectionModel`/`PagedModel`) for lower coupling and less code; reach for extending `RepresentationModel` when a resource deserves an explicit, stable, strongly-typed representation and the extra class earns its keep.

  • Why is making a JPA @Entity extend RepresentationModel a bad idea?
    It couples the persistence layer to the web/HATEOAS concern, drags Spring HATEOAS into the domain, mixes DB mapping with hypermedia, and risks lazy-loading/serialization surprises. Use a DTO or wrap with EntityModel instead.
  • How does RepresentationModelAssemblerSupport help when you extend RepresentationModel?
    It's a base class for assemblers producing custom RepresentationModel subclasses; it offers helpers like `createModelWithId(...)` to instantiate the model and add a self link built from the controller and id, cutting boilerplate.

saying these in an interview costs you the question

  • Claiming you must extend RepresentationModel to attach links
  • Recommending JPA entities extend RepresentationModel
  • Believing the assembler pattern only works with EntityModel and not custom models
  • Treating the choice as purely stylistic with no coupling implications

context