skip to content

What is PagedModel<T>, how does it differ from CollectionModel, and how do you build one from a Spring Data Page?

level: seniorimportance: should knowfreq 50%

answer

  1. PagedModel extends CollectionModel + page block
  2. page = size/totalElements/totalPages/number
  3. first/prev/next/last nav links
  4. PagedResourcesAssembler.toModel(page, assembler)
  5. PageMetadata(size, number, total, pages) order

basics

~10 s

PagedModel<T> is a CollectionModel that also carries page metadata (size, totalElements, totalPages, number) and navigation links (first/prev/next/last). You usually build it from a Spring Data Page using PagedResourcesAssembler.toModel(page, assembler).

solid answer

~40 s

`PagedModel<T>` extends `CollectionModel<T>`, adding a `PageMetadata` record — `size`, `totalElements`, `totalPages`, `number` — serialized under a HAL `page` object, plus the conventional `first`, `prev`, `next`, `last` navigation links. So versus `CollectionModel` you additionally get paging state and generated page-hopping links. You rarely build it by hand: inject `PagedResourcesAssembler<Order>` into the controller, take a `Pageable`, get a Spring Data `Page<Order>`, and call `assembler.toModel(page, entityAssembler)`. The `PagedResourcesAssembler` reads the page's number/size/totals to create the metadata and computes navigation links from the current request URI (honoring the `page`/`size` params). If you must construct it manually, use `PagedModel.of(content, new PagedModel.PageMetadata(size, number, totalElements, totalPages))` and add links yourself.

code

java · 18 lines
java
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;

@GetMapping("/orders")
public PagedModel<EntityModel<Order>> page(Pageable pageable,
        PagedResourcesAssembler<Order> pagedAssembler,
        OrderModelAssembler entityAssembler) {
    Page<Order> page = repository.findAll(pageable);
    return pagedAssembler.toModel(page, entityAssembler);
    // -> _embedded.orders + _links(self,first,next,last) + page{size,totalElements,totalPages,number}
}

// Manual (non-Spring-Data) construction:
// var meta = new PagedModel.PageMetadata(size, number, totalElements, totalPages);
// return PagedModel.of(items, meta, selfLink, nextLink);

go deeper

for a junior

Know PagedModel carries page info and next/prev links; usually made from a Page.

for a middle

Wire PagedResourcesAssembler with a Pageable and describe the HAL page block.

for a senior

Explain metadata derivation, per-item assembler overload, and the constructor-order and proxy gotchas.

for a principal

Decide paged-vs-cursor at the API-contract level (leaking totals, cost of counts) and standardize link generation across services behind proxies.

**What it is.** `PagedModel<T> extends CollectionModel<T>`. On top of the embedded items and collection links it carries pagination state via a nested static class `PagedModel.PageMetadata` with four longs: `size` (page size), `totalElements`, `totalPages`, `number` (current zero-based page index). In HAL this renders as a top-level `page` object. **How it differs from `CollectionModel`.** `CollectionModel` = items + collection links only. `PagedModel` = all of that **plus** the `page` metadata block **plus** the conventional navigation link rels `self`, `first`, `prev`, `next`, `last` (prev/next omitted at the ends). This is what lets a client page through a large result set purely by following links. **`PageMetadata` constructor order (gotcha).** `new PagedModel.PageMetadata(size, number, totalElements, totalPages)` — the order is size, number, totalElements, totalPages. It's easy to swap `number` and `totalElements`; get it wrong and clients compute the wrong page boundaries. **The idiomatic path — `PagedResourcesAssembler`.** Spring Data provides `org.springframework.data.web.PagedResourcesAssembler<T>`, auto-configured as a controller method/argument resolver when Spring Data web support is enabled (Spring Boot does this). Flow: 1. Controller takes a `Pageable` (bound from `?page=&size=&sort=`). 2. Repository returns `Page<Order>` (via `PagingAndSortingRepository`/`JpaRepository`). 3. `assembler.toModel(page)` or `assembler.toModel(page, representationModelAssembler)` produces a `PagedModel`. - The overload with a `RepresentationModelAssembler<Order, EntityModel<Order>>` maps each element (so items get their own links). - The assembler derives `PageMetadata` from the `Page` and builds `first/prev/next/last` from the current request, preserving sort and adjusting `page`. **HAL output shape.** ``` { "_embedded": { "orders": [ ... ] }, "_links": { "self":{...}, "first":{...}, "next":{...}, "last":{...} }, "page": { "size":20, "totalElements":135, "totalPages":7, "number":0 } } ``` **Manual construction.** When not backed by Spring Data (e.g. custom paging), do: ``` PagedModel.PageMetadata meta = new PagedModel.PageMetadata(size, number, total, pages); PagedModel<EntityModel<Order>> model = PagedModel.of(items, meta, selfLink, nextLink); ``` **Gotchas / edge cases.** - **Constructor arg order** (above) is the classic bug. - The `page` block exposes `totalElements` — for very large or security-sensitive datasets you may not want to leak totals; then a `Slice`-based / cursor approach (no total) fits better, but `PagedModel` inherently reports totals. - Navigation links are computed from the *current* request URI; behind a proxy you need correct forwarded headers (`ForwardedHeaderFilter`) or the hrefs will be wrong. - `PagedResourcesAssembler` needs Spring Data web config; without it the argument won't resolve. - Historical naming: pre-1.0 this was `PagedResources`; modern name is `PagedModel`. **When to use.** Any list endpoint backed by pagination where clients should navigate via links and see totals → `PagedModel`. Unpaged hypermedia lists → `CollectionModel`. Single resource → `EntityModel`.

  • What fields are in PagedModel.PageMetadata and in what constructor order?
    `size`, `number`, `totalElements`, `totalPages` — the constructor is `new PageMetadata(size, number, totalElements, totalPages)`. It serializes as `size`, `totalElements`, `totalPages`, `number` in HAL.
  • Why might the next/first/last hrefs be wrong behind a load balancer, and how do you fix it?
    The assembler builds links from the current request URI; a proxy rewrites host/scheme. Enable `ForwardedHeaderFilter` (or set `server.forward-headers-strategy`) so `X-Forwarded-*` headers are honored and hrefs use the external URL.
  • When would you avoid PagedModel in favor of a Slice-style response?
    When computing `totalElements` is expensive or you don't want to expose totals (cursor/infinite-scroll APIs); PagedModel always reports totals, whereas a Slice/cursor approach omits them.

saying these in an interview costs you the question

  • Saying PagedModel and CollectionModel are the same (PagedModel adds the page metadata + nav links)
  • Getting PageMetadata constructor order wrong (swapping number and totalElements)
  • Thinking you must build PageMetadata by hand instead of using PagedResourcesAssembler
  • Assuming nav links work behind a proxy without forwarded-header handling

context