Managing Massive Datasets in Vaadin: Implementing Grid Lazy Loading
Stop crashing your browser with massive tables. Learn how to implement lazy loading in Vaadin Grid to handle millions of rows with a constant memory footprint.
03 Aug 2025, 13:20 UTC

The Memory Wall in Large Data Tables
Loading 50,000 records into a browser-side table is a recipe for a crashed tab or a frozen UI. Even with server-side frameworks, passing a massive list from a database to a UI component consumes excessive JVM heap memory and creates a bottleneck during the initial page render.
The solution is Lazy Loading. Instead of transferring the entire dataset, the UI requests only the specific slice of data currently visible to the user. As the user scrolls, the component requests the next chunk, keeping the memory footprint constant regardless of whether the database contains a thousand or a million rows.
How Vaadin Flow Handles Data Slicing
In Vaadin Flow, the Grid component doesn't hold the data itself; it relies on a DataProvider. While a ListDataProvider loads everything into memory, a callback-based DataProvider acts as a bridge to your backend service.
When the Grid renders, it calculates the viewport size and sends a request to the provider with two critical parameters: the offset (where to start) and the limit (how many items to fetch). This allows the backend to use SQL OFFSET and LIMIT clauses, ensuring the database only processes a tiny fraction of the total records.
Implementing a Callback DataProvider
To implement lazy loading, you must define how to count the total items and how to fetch a specific page. This is typically done using the DataProvider.fromCallbacks method.
// Example implementation in a Vaadin View (Java)
Grid<Customer> grid = new Grid<>(Customer.class);
DataProvider<Customer, Void> dataProvider = DataProvider.fromCallbacks(
query -> {
// 1. Count total items for the scrollbar calculation
return customerService.countAll();
},
query -> {
// 2. Fetch only the requested slice
int offset = query.getOffset();
int limit = query.getLimit();
return customerService.fetchPage(offset, limit).stream();
}
);
grid.setDataProvider(dataProvider);
Execution Details
- Location: Run this within your Vaadin View class on the server.
- Permissions: The
customerServicemust have read access to the underlying data store. - Placeholders: Replace
Customerwith your POJO andcustomerServicewith your actual backend bean. - Risk: If
countAll()is expensive, it will be called frequently. Consider caching the total count if the dataset doesn't change every second.
Handling Sorting and Filtering
Lazy loading becomes complex when users sort columns or filter results. If you sort on the client side, you only sort the visible slice, which is incorrect. To fix this, use the Query object provided in the callback.
The query.getSortOrders() method returns a list of columns the user has clicked. You should pass these sort orders directly into your repository layer (e.g., using Spring Data Sort) so the database handles the ordering before applying the offset and limit.
Trade-offs and Limitations
While lazy loading solves memory issues, it introduces network latency. Every time a user scrolls rapidly, a request is sent to the server. If your backend API is slow, the user will see empty rows (placeholders) momentarily while the data loads.
Another critical limitation involves stateful row components. If you place a TextField inside a Grid row to allow inline editing, that component may be destroyed and recreated as the row scrolls out of view and back in. To persist these changes, you must bind the input to a backing bean or a cache, rather than relying on the UI component's internal state.
Verification Checklist
To verify your implementation is actually lazy loading and not fetching everything:
- Add a log statement inside the
fetchFromBackendcallback to print theoffsetandlimit. - Populate your database with at least 10,000 records.
- Scroll through the Grid; you should see the log trigger multiple times with increasing offset values, rather than one single large fetch at startup.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.