From vaadin-development
Guide Claude on using Vaadin 25 data providers efficiently for Grid, ComboBox, and other listing components. This skill should be used when the user asks to "load data into a grid", "use a data provider", "lazy load data", "paginate grid data", "filter a grid", "sort a grid", "use setItems", "use CallbackDataProvider", "bind a Spring service to a grid", "use setItemsPageable", or needs help with in-memory vs lazy data binding, filtering, sorting, or custom data providers in Vaadin Flow.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vaadin-development:data-providersThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use the Vaadin MCP tools (`search_vaadin_docs`, `get_component_java_api`) to look up the latest documentation whenever uncertain about a specific API detail. Always set `vaadin_version` to `"25"` and `ui_language` to `"java"`.
Use the Vaadin MCP tools (search_vaadin_docs, get_component_java_api) to look up the latest documentation whenever uncertain about a specific API detail. Always set vaadin_version to "25" and ui_language to "java".
List<Person> people = personService.findAll();
grid.setItems(people);
Use when: the full dataset is small (hundreds to low thousands of items), or you've already loaded it for other reasons.
In-memory data supports client-side-like sorting and filtering through the ListDataView:
GridListDataView<Person> dataView = grid.setItems(people);
dataView.setFilter(p -> p.getAge() < 30);
dataView.setSortOrder(Person::getName, SortDirection.ASCENDING);
Only the visible portion of data is loaded. Filtering and sorting are delegated to the backend (typically a database).
With Spring (preferred — uses Pageable):
grid.setItemsPageable(personService::list);
// Service method signature:
public List<Person> list(Pageable pageable) {
return repository.findAll(pageable).getContent();
}
setItemsPageable converts Grid's internal query into a Spring Pageable with offset, limit, and sort information. This integrates directly with Spring Data repositories.
Without Spring (generic callbacks):
grid.setItems(query ->
personService.fetch(query.getOffset(), query.getLimit()).stream()
);
The callback receives a Query object with getOffset(), getLimit(), and getSortOrders().
GridListDataView<Person> dataView = grid.setItems(allPeople);
filterField.addValueChangeListener(e ->
dataView.setFilter(person ->
person.getName().toLowerCase().contains(e.getValue().toLowerCase()))
);
Pass the filter value into your service callback:
// With Spring
GridLazyDataView<Person> dataView = grid.setItemsPageable(
pageable -> personService.list(pageable, filterField.getValue())
);
filterField.setValueChangeMode(ValueChangeMode.LAZY);
filterField.addValueChangeListener(e -> dataView.refreshAll());
// Without Spring
grid.setItems(query ->
personService.fetch(
query.getOffset(),
query.getLimit(),
query.getSortOrders(),
filterField.getValue()
).stream()
);
filterField.addValueChangeListener(e -> grid.getDataProvider().refreshAll());
Use ValueChangeMode.LAZY on the filter field — it waits for the user to pause typing before triggering, reducing backend calls.
Columns with Comparable types are automatically sortable. Customize with a Comparator:
grid.addColumn(Person::getName)
.setHeader("Name")
.setComparator(Comparator.comparing(p -> p.getName().toLowerCase()));
Declare which columns are sortable:
grid.setSortableColumns("name", "email"); // by property name
// or per-column:
grid.addColumn(Person::getTitle).setKey("title").setSortable(true);
Then handle query.getSortOrders() in your callback. With setItemsPageable, sorting is handled automatically via the Pageable object.
ComboBox supports lazy loading with built-in text filtering:
// With Spring
comboBox.setItemsPageable(productService::list);
// Service must accept filter string:
public List<Product> list(Pageable pageable, String filterString) { ... }
// Without Spring
comboBox.setItems(query ->
personService.fetch(
query.getOffset(),
query.getLimit(),
query.getFilter().orElse("")
).stream()
);
Without a count callback, Grid uses infinite scrolling (the scrollbar updates as more items are loaded). For a better UX, provide the count:
// With Spring
grid.setItemsPageable(personService::list, personService::count);
// Without Spring
grid.setItems(
query -> personService.fetch(query.getOffset(), query.getLimit()).stream(),
query -> personService.count()
);
If exact count is expensive, provide an estimate:
GridLazyDataView<Person> dataView = grid.setItems(fetchCallback);
dataView.setItemCountEstimate(1000);
dataView.setItemCountEstimateIncrease(500);
person.setEmail("[email protected]");
dataView.refreshItem(person); // updates just this row
dataView.refreshAll(); // re-fetches everything
GridListDataView<Person> dataView = grid.setItems(mutableList);
dataView.addItem(newPerson);
dataView.removeItem(oldPerson);
Components rely on hashCode() and equals() for item identity. These must be based on stable, unique properties (typically the database ID), not mutable fields:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id; // stable identifier only
}
@Override
public int hashCode() {
return Objects.hash(id);
}
With Lombok: use @EqualsAndHashCode(onlyExplicitlyIncluded = true) and @EqualsAndHashCode.Include on the ID field.
@SpringComponent
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PersonGrid extends Grid<Person> {
public PersonGrid(@Autowired PersonRepository repo) {
super(Person.class);
setItems(q -> repo.findAll(
PageRequest.of(q.getPage(), q.getPageSize())).stream());
setColumns("name", "email");
}
}
@SpringComponent
public class PersonDataProvider
extends AbstractBackEndDataProvider<Person, String> {
@Autowired
private PersonRepository repo;
@Override
protected Stream<Person> fetchFromBackEnd(Query<Person, String> query) {
String filter = query.getFilter().orElse("");
return repo.findByNameContaining(filter,
VaadinSpringDataHelpers.toSpringPageRequest(query)).stream();
}
@Override
protected int sizeInBackEnd(Query<Person, String> query) {
String filter = query.getFilter().orElse("");
return (int) repo.countByNameContaining(filter);
}
}
setItemsPageable with Spring — it handles offset/limit/sort conversion to Pageable automatically, integrating cleanly with Spring Data.hashCode/equals — based on the entity's ID, not mutable fields. This is required for Grid to correctly track selections and updates.ValueChangeMode.LAZY on filter fields — prevents excessive backend queries while the user is typing.refreshAll() when filter/sort criteria change externally — the Grid doesn't automatically know when your filter field value changes.getGenericDataView().getItems() on lazy data — it loads the entire dataset into memory, defeating the purpose of lazy loading.npx claudepluginhub marcushellberg/vaadin-development-plugin --plugin vaadin-developmentImplements use cases by creating Vaadin views, forms, grids for UI layer and jOOQ queries for data access in Java web apps.
Virtualizes MUI lists with react-window FixedSizeList/VariableSizeList, react-virtuoso, and Autocomplete patterns for rendering 1000+ items without layout thrashing or memory issues.
Builds headless data tables with TanStack Table v8 featuring server-side pagination, filtering, sorting, virtualization for Cloudflare Workers + D1 databases and TanStack Query integration.