Paging

Paging is the standard request-side pagination params class.

Bind it with @RequestBean Paging and it captures the page/size/order query parameters, then converts to a Micronaut Pageable on demand.

Query parameters

Parameter Default Notes

page

0

Zero-based page index. Clamped to 0 when negative.

size

25

Page size, defaulting to Paging.DEFAULT_PAGE_SIZE (25). Capped at Paging.MAX_PAGE_SIZE (200).

orderByField

none

Field to order by. Validate it against an allow-list — see Query-param smuggle-rejection.

orderByDirection

DESC

ASC or DESC. Applied only when orderByField is set.

Converting to a Pageable

toPageable() clamps page and size and applies ordering only when orderByField is set:

Pageable pageable = paging.toPageable();

toPageableWithDefaults(field, direction) supplies a default order, but only when the client did not request one:

Pageable pageable = paging.toPageableWithDefaults("createdAt", Direction.DESC);

Producing a paginated envelope

Envelopes.slice(…​) turns a Micronaut Page or Slice into a SliceEnvelope, setting content, pageNumber, and size:

var page = service.find(query, paging.toPageable());

return Envelopes.slice(page).withTenantId(tenantId);

pageNumber (zero-based) and size mirror the keys of Micronaut’s Page/Slice serialization.

Continuing by cursor

A slice continued from a position instead of an offset is built with Envelopes.cursored(…​): no pageNumber, and nextCursor names the last member returned.

return Envelopes.cursored(members, size, members.isEmpty() ? null : members.get(members.size() - 1).id());

Three rules decide nextCursor:

  • Every response the client can continue from carries it, the first slice included.

  • No nextCursor is the end; the client stops.

  • A nextCursor on empty content is a position to ask again from later, for an endpoint the client polls; repeat the cursor the request carried.

An empty first slice has no position and omits nextCursor either way. Whether an endpoint ends on empty content or keeps the cursor for polling is declared in its OpenAPI description.

Envelopes.slice(…​) refuses a slice read with a cursored Pageable, Micronaut’s CursoredPage included, since its page number is not the client’s position; build the envelope with cursored and the cursor the endpoint chose.

Paging does not bind a cursor query value. Bind it on the endpoint’s own @Introspected query bean and pass that bean to QueryParamGuard.fromBeans(…​) beside Paging, or the guard rejects it — see Query-param smuggle-rejection.