SuperAPI AI Directory

data-providers

Data & analytics

View repository

Updated 10-16 07:26

About this skill

Vaadin Data Providers in Vaadin 25

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".

Two Approaches: In-Memory vs. Lazy Loading

In-memory — small datasets that fit in server memory

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);

Lazy loading — large datasets loaded on demand

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.findAllBy(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.

Use Slice instead of Page: The repository method should return a Slice, not a Page. A Page executes an additional COUNT query that Grid cannot use — Grid treats counting as a separate concern via a dedicated count callback. Use a Slice-returning repository method (e.g., Slice<Person> findAllBy(Pageable pageable)) and provide a separate count method when needed.

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().

Filtering

In-memory filtering

GridListDataView<Person> dataView = grid.setItems(allPeople);

filterField.addValueChangeListener(e ->
    dataView.setFilter(person ->
        person.getName().toLowerCase().contains(e.getValue().toLowerCase()))
);

Lazy filtering (backend-delegated)

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.

Sorting

In-memory sorting

Columns with Comparable types are automatically sortable. Customize with a Comparator:

grid.addColumn(Person::getName)
    .setHeader("Name")
    .setComparator(Comparator.comparing(p -> p.getName().toLowerCase()));

Lazy sorting (backend-delegated)

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 Lazy Loading

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()
);

Providing Item Count

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);

Updating Displayed Data

Refresh a single item

person.setEmail("new@example.com");
dataView.refreshItem(person);  // updates just this row

Refresh all data

dataView.refreshAll();  // re-fetches everything

Mutating in-memory lists

GridListDataView<Person> dataView = grid.setItems(mutableList);
dataView.addItem(newPerson);
dataView.removeItem(oldPerson);

Item Identity

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 Objects.equals(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.

The ID field must be final (e.g., private final Long id;) so that the hash code never changes after construction. A mutable ID violates the hashCode() contract and breaks Grid's internal identity tracking.

Reusing Data Binding Logic

Do NOT Spring-manage Grids or DataProviders. They hold UI state and are tied to a single user session. Create them as plain classes, pass dependencies via constructors.

Domain-specific Grid subclass

public class PersonGrid extends Grid<Person> {

    public PersonGrid(PersonRepository repo) {
        super(Person.class);
        setItemsPageable(pageable -> repo.findAllBy(pageable).getContent());
        setColumns("name", "email");
    }
}

// In the view:
@Route("people")
@PermitAll
public class PeopleView extends VerticalLayout {

    public PeopleView(PersonRepository repo) {
        add(new PersonGrid(repo));
    }
}

Standalone DataProvider class

public class PersonDataProvider
        extends AbstractBackEndDataProvider<Person, String> {

    private final PersonRepository repo;

    public PersonDataProvider(PersonRepository repo) {
        this.repo = 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);
    }
}

Best Practices

  1. Use setItemsPageable with Spring — it handles offset/limit/sort conversion to Pageable automatically, integrating cleanly with Spring Data.
  2. Use lazy loading for datasets over ~1000 items — in-memory is simpler but doesn't scale.
  3. Always implement stable hashCode/equals — based on the entity's ID, not mutable fields. This is required for Grid to correctly track selections and updates.
  4. Declare column-name constants on the entity or DTO — string keys like "name" used in setKey(), setSortableColumns(), and sort-order handling are prone to typos. Define constants (e.g., public static final String COL_NAME = "name";) on the entity class and reference them in the view. This makes refactoring safer and enables compile-time checks.
  5. Use ValueChangeMode.LAZY on filter fields — prevents excessive backend queries while the user is typing.
  6. Provide a count callback when possible — it gives users a proper scrollbar and ability to jump to the end.
  7. Call refreshAll() when filter/sort criteria change externally — the Grid doesn't automatically know when your filter field value changes.
  8. Don't call getGenericDataView().getItems() on lazy data — it loads the entire dataset into memory, defeating the purpose of lazy loading.