Docs

Documentation versions (currently viewingVaadin 24)

User Interface Threads Flow

How to use threads in a Vaadin Flow user interface.

Developers often use server push to update the user interface from background jobs (see Background Jobs - UI Interaction). However, in Vaadin Flow, there are also cases where you may want to start a separate thread for use by the user interface itself. You might, for instance, want to show the server date and time in "real time".

If you have experience with Swing, you might be tempted to use a Timer, or to start a new Thread, manually. In Flow, this isn’t a good idea. Flow applications are multi-user applications, with potentially thousands of concurrent users. If each user creates their own Timer, or starts their own Thread, you may run out of threads. If that happens, the application crashes.

As a better strategy, use virtual threads, or Spring’s TaskExecutor and TaskScheduler. These are explained in the following sections, with some examples.

Note
The examples on this page only work with push enabled. For information about how to do that, see the Server Push documentation page.

Virtual Threads

If you use a Java version that supports virtual threads, you can start a new virtual thread whenever you need one.

Here is an example of a button click listener that starts a new virtual thread:

button.addClickListener(clickEvent -> {
    Thread.ofVirtual().start(UI.getCurrent().accessLater(() -> {
        // Perform the UI operation here.
    }, null));
});

This is the easiest way of starting a new user interface thread. If you’re able to use virtual threads, they should be your first choice. If you run into problems, though, switch to the TaskExecutor.

For scheduled tasks, you should still use the TaskScheduler. This is covered later on this page.

Task Executor

You can use Spring’s TaskExecutor and TaskScheduler to start tasks directly from the user interface, as well. Configuring them is covered in the Background Jobs documentation page.

Before starting a task with TaskExecutor and TaskScheduler, you should make sure it’s actually UI-related and not a background job. To use them properly, you would inject them into your view, and then call them when needed.

Here is an example of a view that gets the TaskExecutor as a constructor parameter:

@Route
public class MyView extends VerticalLayout {

    private final TaskExecutor taskExecutor;

    public MyView(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
        ...
    }
}

This example uses a button click listener that starts a UI operation in a background thread:

button.addClickListener(clickEvent -> {
    taskExecutor.execute(UI.getCurrent().accessLater(() -> {
        // Perform the UI operation here.
    }, null));
});

Because of the call to UI.accessLater(), the user interface is updated through a server push when the task finishes.

Caution
Don’t use the @Async annotation in Flow views. It turns them into proxies that don’t work with Vaadin.

Task Scheduler

You can use the TaskScheduler to schedule tasks. With it, you have to schedule the task when the component is attached, and cancel it when it’s detached.

The following example schedules a task to be executed every second. The task sets the text of currentTimeLabel to the current date and time of the server. When the component is detached, the task is cancelled:

@Override
protected void onAttach(AttachEvent attachEvent) {
    var task = taskScheduler.scheduleAtFixedRate(
        attachEvent.getUI().accessLater(() -> {
            currentTimeLabel.setText(Instant.now().toString());
        }, null), Duration.ofSeconds(1)
    );
    addDetachListener(detachEvent -> {
        detachEvent.unregisterListener();
        task.cancel(true);
    });
}

The tasks that you execute in the task scheduler should be fast. If you need to schedule long-running tasks, you should give them to TaskExecutor for execution.

Caution
Do not use the @Scheduled annotation in Flow views. It turns them into proxies that don’t work with Vaadin.