Skip to content

ListingBackend

ListingBackend<Filters, Row> is the server-side contract for a grid. Implement it to supply paginated, searchable, and filterable rows to a Listing component. The only method you must provide is search; everything else has sensible defaults.

public interface ListingBackend<Filters, Row> extends ActionHandler, ActionSupplier {
ListingData<Row> search(
String searchText,
Filters filters,
Pageable pageable,
HttpRequest httpRequest);
default boolean selectionEnabled() { return false; }
default Class<Filters> filtersClass() { /* auto-inferred via generics */ }
}
ParameterDescription
FiltersA class or record whose fields become the filter form rendered above the grid
RowA class or record whose fields become the grid columns
MethodDescription
search(searchText, filters, pageable, httpRequest)Required. Return a page of rows matching the search criteria
selectionEnabled()Return true to enable row checkbox selection
filtersClass()Returns the Filters class; auto-inferred via generics, rarely overridden
public record Pageable(int page, int size, List<Sort> sort) {}

Mateu populates this from the grid state automatically. sort contains zero or more Sort entries, each with a fieldId and a Direction (ASC or DESC).

public record ListingData<Row>(Page<Row> page, String emptyStateMessage) {}

emptyStateMessage is optional; when set it is shown in the grid when there are no rows.

Factory methods:

MethodDescription
ListingData.of(rows...)Wrap a varargs array of rows into a single-page result
ListingData.of(List<Row>)Wrap a list of rows into a single-page result
ListingData.from(List<Row>)Alias for of(List<Row>)
ListingData.builder()...build()Builder pattern — use when you need to set all Page fields manually
public record Page<T>(
String searchSignature,
int pageSize,
int pageNumber,
long totalElements,
List<T> content) {}

searchSignature is an opaque token (typically the search text) used by the grid to detect when the result set has changed. totalElements drives the pagination footer.

From the Changes demo — a listing of content changes with a toolbar action that reads JWT claims from the Authorization header:

@Title("Changes")
@Service
@Scope("prototype")
@Trigger(type = TriggerType.OnLoad, actionId = "search")
@Style("max-width:900px;margin: auto;")
public class Changes extends Listing<NoFilters, ChangeRow> {
final ChangeQueryService queryService;
final CreateReleaseForm createReleaseForm;
@Override
public ListingData<ChangeRow> search(
String searchText, NoFilters filters, Pageable pageable, HttpRequest httpRequest) {
var found = queryService.findAll(searchText, filters, pageable);
return ListingData.<ChangeRow>builder()
.page(Page.<ChangeRow>builder()
.searchSignature(found.page().searchSignature())
.totalElements(found.page().totalElements())
.pageSize(found.page().pageSize())
.pageNumber(found.page().pageNumber())
.content(found.page().content().stream()
.map(dto -> new ChangeRow(
dto.pageId(), dto.page(), dto.country(), dto.language(),
new Status(mapStatus(dto.status()), dto.status().name()),
new ColumnAction("compare", "Compare")))
.toList())
.build())
.build();
}
}

When all rows fit in memory and you do not need server-side pagination:

@Override
public ListingData<CustomerRow> search(
String searchText, CustomerFilters filters,
Pageable pageable, HttpRequest httpRequest) {
var rows = repository.findAll().stream()
.filter(c -> searchText == null || c.name().contains(searchText))
.map(c -> new CustomerRow(c.id(), c.name(), c.email()))
.toList();
return ListingData.of(rows);
}

ReactiveListingBackend<Filters, Row> is the Project Reactor variant. Use it when your data source is reactive (R2DBC, WebClient, etc.). The contract is identical to ListingBackend except that search returns Mono<ListingData<Row>> and handleAction returns Flux<Object>.

public interface ReactiveListingBackend<Filters, Row> extends ActionHandler {
Mono<ListingData<Row>> search(
String searchText,
Filters filters,
Pageable pageable,
HttpRequest httpRequest);
default boolean selectionEnabled() { return false; }
}
@Override
public Mono<ListingData<ProductRow>> search(
String searchText, ProductFilters filters,
Pageable pageable, HttpRequest httpRequest) {
return productRepository.findAll(searchText, pageable)
.collectList()
.map(rows -> ListingData.of(rows));
}