Docs

Documentation versions (currently viewingVaadin 25.4 (pre-release))

User Interface Threads

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:

Source code
Java
button.addClickListener(clickEvent -> {
    var ui = UI.getCurrentOrThrow();
    Thread.ofVirtual().start(() -> {
        // Do the work here
        ui.access(() -> {
            // Update the UI here
        });
    });
});

Do the work in the thread itself, and use UI.access() only for updating the user interface. The command that you pass to access() runs while the user session is locked. If you do slow work inside it, you block all other requests from the same user until it finishes. For more information, see Getting the UI Instance.

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:

Source code
Java
@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:

Source code
Java
button.addClickListener(clickEvent -> {
    var ui = UI.getCurrentOrThrow();
    taskExecutor.execute(() -> {
        // Do the work here
        ui.access(() -> {
            // Update the UI here
        });
    });
});

Because of the call to UI.access(), 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:

Source code
Java
@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.

Updating the Scheduled Result With a Signal

If the scheduled task only updates UI state, you can hold that state in a signal and skip UI.accessLater() entirely. Bind the component to the signal once, then update the signal from the scheduled task. Flow pushes the change to the bound component automatically:

Source code
Java
private final ValueSignal<String> currentTime = new ValueSignal<>("");

public MyView(TaskScheduler taskScheduler) {
    this.taskScheduler = taskScheduler;
    currentTimeLabel.bindText(currentTime); 1
}

@Override
protected void onAttach(AttachEvent attachEvent) {
    var task = taskScheduler.scheduleAtFixedRate(
        () -> currentTime.set(Instant.now().toString()), 2
        Duration.ofSeconds(1));
    addDetachListener(detachEvent -> {
        detachEvent.unregisterListener();
        task.cancel(true); 3
    });
}
  1. Bind the label to the signal once. The binding is active only while the component is attached and is removed automatically on detach.

  2. The scheduled task updates the signal directly — no UI.accessLater() and no captured UI instance.

  3. You still cancel the scheduled task on detach. Signals manage the binding lifecycle, not the scheduler.

For more on binding components to signals, see Pushing State with Signals.

Calling Protected Services

Spring Security keeps the current user in a SecurityContext that’s bound to the request thread. The threads described on this page don’t inherit it. Suppose the work calls an application service that’s protected with method security, as described in Protect Services. Spring then denies access, even though the same call works in a click listener. The user gets no feedback, as the error only shows up in the log.

To run the work as the current user, wrap it in a DelegatingSecurityContextRunnable from the org.springframework.security.concurrent package. Create the wrapper while you’re still in the request thread, so that it captures the security context of the request:

Source code
Java
button.addClickListener(clickEvent -> {
    var ui = UI.getCurrentOrThrow();
    Thread.ofVirtual().start(new DelegatingSecurityContextRunnable(() -> { 1
        var report = reportService.generateReport(); 2
        ui.access(() -> {
            // Update the UI here
        });
    }));
});
  1. The wrapper is created in the click listener, where the security context of the current user is available.

  2. The protected service sees the same user as it would in the click listener.

The same wrapper works for tasks that you give to the TaskExecutor. For other ways of propagating the security context, see Concurrency Support in the Spring Security Reference Manual.

This applies to work that a user starts from the UI. Background jobs that run independently of any user shouldn’t rely on method security at all, as explained in Jobs.