Skip to content

How to Use Asynchronous Updates in Vaadin 8

Tatu Lund edited this page Feb 6, 2026 · 1 revision

How to Use Asynchronous Updates in Vaadin 8

This guide shows a practical pattern for running background work in Vaadin 8 and safely updating the UI using Vaadin Push and a shared ExecutorService. The examples come from the statistics dashboard in this project (StatsPresenter, StatsView, Utils.access, AccessTask, and VaadinCreateUI).

The same pattern can be reused for long-running tasks like report generation, batch imports, or slow external API calls.


1. Enable Push and a Shared Executor per UI

Vaadin 8 UIs must be explicitly configured for push when you want to update them from a background thread.

In this application, the main UI is annotated with @Push and exposes a per-UI executor:

@Push(transport = Transport.WEBSOCKET_XHR)
public class VaadinCreateUI extends UI implements EventBusListener {

    private ExecutorService executor;

    public ExecutorService getExecutor() {
        if (executor == null) {
            executor = Executors.newSingleThreadExecutor(
                    Thread.ofVirtual().name("user-job").factory());

            executor.execute(() -> {
                if (!executor.isShutdown()) {
                    User user = (User) getSession().getSession()
                            .getAttribute(CurrentUser.CURRENT_USER_SESSION_ATTRIBUTE_KEY);
                    if (user != null) {
                        String userId = String.format("[%s/%s]",
                                user.getRole(), user.getName());
                        MDC.put("userId", userId);
                    }
                }
            });
        }
        return executor;
    }

    @Override
    public void detach() {
        super.detach();
        shutdownExecutor();
    }
}

Key ideas:

  • Push enabled: @Push allows background threads to push UI changes to the browser.
  • One executor per UI: getExecutor() lazily creates a single-thread executor for this UI.
  • Virtual threads: the executor uses Thread.ofVirtual() to keep background tasks lightweight.
  • Cleanup: detach() shuts down the executor when the UI is closed.

Using a single-thread executor per UI also caps the amount of parallel background work each user can trigger, preventing a single session from flooding the server with jobs and improving overall scalability.


2. Centralized, Safe UI Access with Utils.access

Vaadin requires all UI changes to run in the UI’s lock via UI.access(...). This project wraps that pattern in a utility method and an error-handling task.

public final class Utils {

    public static void access(@Nullable UI ui, Runnable command) {
        if (ui != null) {
            ui.access(new AccessTask(command));
        } else {
            logger.warn("No UI available for pushing updates.");
        }
    }
}

public class AccessTask implements ErrorHandlingRunnable {

    private final Runnable command;

    public AccessTask(Runnable command) {
        this.command = command;
    }

    @Override
    public void run() {
        command.run();
    }

    @Override
    public void handleError(Exception exception) {
        if (exception instanceof UIDetachedException) {
            logger.info("Browser window was closed while pushing updates.");
        } else {
            logger.error("Error while pushing updates", exception);
        }
    }
}

Benefits of this pattern:

  • All background callbacks call Utils.access(ui, ...), not UI.access(...) directly.
  • If the UI is detached (browser closed), UIDetachedException is logged as info, not as a test-breaking error.
  • If any other exception occurs during a push, it’s logged with stack trace.

3. Running Background Work in a Presenter (StatsPresenter)

The statistics dashboard fetches data and computes aggregates in a background thread, then updates the view via push.

public class StatsPresenter implements EventBusListener, Serializable {

    private final StatsView view;
    private CompletableFuture<Void> future;
    private ProductDataService service = VaadinCreateUI.get().getProductService();
    private ExecutorService executor = VaadinCreateUI.get().getExecutor();

    record ProductData(Collection<Product> products,
                       Collection<Category> categories) {}

    public StatsPresenter(StatsView view) {
        this.view = view;
        getEventBus().registerEventBusListener(this);
    }

    private CompletableFuture<ProductData> loadProductsAsync() {
        var productService = getService();
        return CompletableFuture.supplyAsync(
                () -> new ProductData(productService.getAllProducts(),
                                       productService.getAllCategories()),
                getExecutor());
    }

    public void requestUpdateStats() {
        future = loadProductsAsync()
                .thenAccept(this::calculateStatistics)
                .whenComplete((result, throwable) -> future = null);
    }

    private void calculateStatistics(ProductData productData) {
        var stats = new ProductStatistics(
                StatsUtils.calculateAvailabilityStats(productData.products()),
                StatsUtils.calculateCategoryStats(productData.categories(),
                                                  productData.products()),
                StatsUtils.calculatePriceStats(productData.products()));

        view.updateStatsAsync(stats);
    }

    public void cancelUpdateStats() {
        getEventBus().unregisterEventBusListener(this);
        if (future != null) {
            future.cancel(true);
            future = null;
        }
    }
}

Notable points:

  • Background work (loadProductsAsync) is offloaded to the per-UI executor.
  • Computation (calculateStatistics) runs on that background thread.
  • UI update is delegated to the view via view.updateStatsAsync(stats).
  • Cancellation is supported when the view is detached.

An important detail is that no backend or business logic runs inside the UI access block: all fetching and aggregation happen in the presenter on a background thread, and the view’s updateStatsAsync(...) only applies precomputed values via Utils.access(ui, ...). This preserves MVP separation of concerns and keeps the UI lock and request thread usage very short.


4. Updating the View via Push (StatsView)

The view itself never blocks in the constructor; it only triggers background work when navigation is complete and updates itself through Utils.access.

4.1 Kicking Off the Async Load in enter()

public class StatsView extends VerticalLayout implements VaadinCreateView {

    private final StatsPresenter presenter = new StatsPresenter(this);

    @Override
    public void enter(ViewChangeEvent event) {
        openingView(VIEW_NAME);
        presenter.requestUpdateStats();
    }
}

Calling the backend from enter(...) (instead of in the view constructor) ensures that navigation has completed, access control has verified the user may see this view, and the UI is fully initialized before any background work starts, which avoids wasted work for views the user never actually enters.

4.2 Applying the Results on the UI Thread

public void updateStatsAsync(ProductStatistics stats) {
    Utils.access(ui, () -> {
        if (isAttached()) {
            updateAvailabilityChart(stats.availabilityStats());
            updateCategoryChart(stats.categoryStats());
            updatePriceChart(stats.priceStats());

            availabilityChart.drawChart();
            categoryChart.drawChart();
            priceChart.drawChart();

            dashboard.addStyleName("loaded");
        }
    });
}

public void setLoadingAsync() {
    Utils.access(ui, () -> {
        dashboard.removeStyleName("loaded");
        categoryChart.getConfiguration().removeyAxes();
    });
}

Key practices:

  • All chart updates and UI state changes happen inside Utils.access(ui, ...).
  • isAttached() is checked before applying state to avoid race conditions with detaching views.
  • A simple CSS flag (loaded) is used to control client-side loading indicators.

4.3 Cleaning Up on Detach

@Override
public void attach() {
    super.attach();
    ui = getUI();
    resizeListener = ui.getPage().addBrowserWindowResizeListener(
            e -> JavaScript.eval("vaadin.forceLayout()"));
}

@Override
public void detach() {
    super.detach();
    resizeListener.remove();
    presenter.cancelUpdateStats();
    ui = null;
}

The view:

  • Remembers its UI only while attached.
  • Cancels any outstanding statistics fetch when detached.

The UI reference is captured in attach() because getUI() is not fully thread safe and should not be looked up from arbitrary background callbacks. By cancelling the CompletableFuture in detach(), the presenter stops any in-flight backend work once the view is no longer visible, conserving server resources. Detach is triggered both when navigating away from the view and when the browser tab is closed; in the latest Vaadin 8 versions the UI is closed immediately on tab close instead of waiting for multiple missed heartbeats.


5. Putting It All Together: Recommended Pattern

The combination used in this application is a solid, reusable pattern for asynchronous updates in Vaadin 8:

  1. Enable Push and per-UI executor

    • Annotate the UI with @Push.
    • Provide getExecutor() that returns a shared ExecutorService (virtual thread pool recommended).
    • Shut down the executor in detach().
  2. Centralize UI access and error handling

    • Wrap UI.access in a helper like Utils.access(ui, command).
    • Use an ErrorHandlingRunnable (AccessTask) to log UIDetachedException cleanly.
  3. Keep heavy work off the UI thread

    • In presenters, use CompletableFuture.supplyAsync(..., getExecutor()) for I/O and CPU-heavy work.
    • Pass results to the view using a small DTO/record.
  4. Update the view only on the UI thread

    • Expose methods like updateStatsAsync(...) and setLoadingAsync() in the view.
    • Inside those methods, use Utils.access(ui, ...) to apply changes.
  5. Handle lifecycle and cancellation

    • Start background work in enter(...), not in constructors.
    • Cancel pending operations and unregister listeners in detach().

Following this pattern keeps your Vaadin 8 application:

  • Responsive (no long blocks on the UI thread)
  • Robust (safe handling of closed browser windows)
  • Observable (executor threads carry user information in logging MDC)
  • Reusable (presenters encapsulate async logic, views focus on rendering)

Clone this wiki locally