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 |
|---|---|---|
|
|
Zero-based page index. Clamped to |
|
|
Page size, defaulting to |
|
none |
Field to order by. Validate it against an allow-list — see Query-param smuggle-rejection. |
|
|
|
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
nextCursoris the end; the client stops. -
A
nextCursoron emptycontentis 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.