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?
answer
- extend = named typed model, more coupling
- wrap = pure DTO, HATEOAS only at edge
- never make @Entity extend RepresentationModel
- assembler pattern works for both
- default wrap, extend for first-class contracts
basics
~20 sExtend 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 sBoth 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// 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
Know both approaches exist; wrapping keeps DTOs plain.
Articulate the coupling vs explicit-typing trade-off and avoid entities extending the base.
Drive the decision per resource and wire assemblers for either target type.
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