Pushing UI Updates
- Pushing State with Signals
- Pushing Manually With UI.access()
- Avoiding Floods
- Avoiding Unnecessary Pushes
Whenever you’re using server push in Vaadin Flow, you’re triggering it from a thread other than the normal HTTP request thread. Making changes to a UI from another thread and pushing them to the browser requires locking the user session. Otherwise, the UI update performed from another thread could conflict with a regular event-driven update and cause either data corruption, race conditions or deadlocks.
Such errors are by nature hard to discover and fix, since they often occur randomly and under a heavy load.
There are two ways to push these updates:
-
For state-driven updates, use signals. Signals are thread-safe and update any bound components automatically, so you don’t have to lock the session yourself. This is the recommended approach for most cases, and the one this page leads with.
-
For imperative updates — such as adding or removing components, or calling component methods that aren’t expressed as state — lock the session manually with the
UI.access()method. UnderstandingUI.access()also helps you understand what signals do for you under the hood.
|
Note
| The examples on this page only work with push enabled. This applies to signal-driven updates as well: when a signal is changed from a background thread, push is what delivers the change to the browser. For information about how to enable push, see the Server Push documentation page. |
Pushing State with Signals
A signal holds a piece of UI state. When you bind a component to a signal, the component updates itself whenever the signal’s value changes — no matter which thread made the change. Signals are thread-safe and synchronize with the UI internally, so you don’t need to call UI.access(), and you don’t need to capture the UI instance in advance.
Just as importantly, bindings are tied to the component lifecycle: a binding is active only while the component is attached, and it’s removed automatically when the component is detached. This means there’s no listener to unsubscribe and no UI reference left dangling, so the whole class of memory leaks described later on this page doesn’t arise.
Updating From a Background Job
Hold the state that the background job produces in a signal, and bind a component to it. When the job updates the signal from its own thread, the bound component updates automatically:
Source code
Java
public class JobView extends VerticalLayout {
private final ValueSignal<String> status = new ValueSignal<>("Idle");
public JobView(MyService myService) {
Span statusLabel = new Span();
statusLabel.bindText(status); 1
Button start = new Button("Start job", click ->
myService.startBackgroundJob(() -> status.set("Done"))); 2
add(statusLabel, start);
}
}-
The label’s text is bound to the
statussignal. -
The callback runs on a background thread and updates the signal directly. There’s no
UI.access()and no capturedUIinstance — the bound label updates on its own.
Broadcasting to All Users
For state that several users share, put a shared signal in a Spring bean and let the view bind to it. A singleton bean shares one signal instance across the whole application, so a change made by one user — or by a background thread — is pushed to every connected user that’s bound to it:
Source code
Java
@Component // Singleton: one instance shared by all users
public class ChatMessages {
private final SharedListSignal<String> messages =
new SharedListSignal<>(String.class);
public SharedListSignal<String> messages() {
return messages;
}
public void post(String message) {
messages.insertLast(message);
}
}The view binds its component list to the shared signal with bindChildren():
Source code
Java
public class ChatView extends VerticalLayout {
public ChatView(ChatMessages chatMessages) {
VerticalLayout list = new VerticalLayout();
list.bindChildren(chatMessages.messages(), messageSignal -> {
Span message = new Span();
message.bindText(messageSignal); 1
return message;
});
add(list);
TextField input = new TextField();
Button send = new Button("Send", click -> {
chatMessages.post(input.getValue());
input.clear();
});
add(input, send);
}
}-
Pass the child signal straight to a component instead of reading it with
get(). CallingmessageSignal.get()inside the factory throws an exception, because the factory isn’t itself a reactive context — let the component create its own binding.
When any user calls post(), every open ChatView receives the new message. Compare this with the manual approach described in Avoiding Memory Leaks below: there’s no event bus to subscribe to, no UI.access(), and no detach handler to unsubscribe. The signal lives in the bean, so its lifetime is the bean’s lifetime, and each view’s binding is cleaned up automatically on detach.
|
Note
|
Use a @Component singleton for application-wide state, or a @VaadinSessionScope bean for state shared between the tabs of a single user. For more on scoping signals, see Signal Scope Patterns.
|
Highlighting Background Changes
Sometimes you want to draw attention to a value that changed because of a background update, rather than one the current user triggered. Binding methods provide an onChange() callback whose context can tell the two apart:
Source code
Java
Span price = new Span();
price.bindText(priceSignal.map(p -> "$" + p))
.onChange(ctx -> {
if (ctx.isBackgroundChange()) {
ctx.getElement().flashClass("highlight"); 1
}
});-
isBackgroundChange()returnstruewhen the change came from another session or a background thread. See Effects and Computed Signals for details.
Pushing Manually With UI.access()
Signals cover state-driven updates. When you need to make an imperative change from a background thread — for example, adding components to a layout — lock the session yourself with UI.access():
Source code
Java
ui.access(() -> {
// Update your UI here
});By default, Flow uses automatic pushing. This means that any pending changes are pushed to the browser after the command passed to UI.access() finishes. You can also configure Flow to use manual pushing. This would give you more control over when changes are pushed to the browser. For example, you can push multiple times inside a single call to UI.access().
To enable manual pushing, you have to make an addition to the @Push annotation, like this:
Source code
Java
@Push(PushMode.MANUAL)
public class Application implements AppShellConfigurator {
...
}Afterwards, you’ll have to call the UI.push() method whenever you want to push your changes to the browser, like this:
Source code
Java
ui.access(() -> {
// Update your UI here
ui.push();
});Getting the UI Instance
Before you can call access(), you need to get the UI instance. You’d typically use Component.getUI() or UI.getCurrent() for this. However, both are problematic when it comes to server push.
Component.getUI() is not thread-safe, which means you should only call it while the user session is locked. Therefore, you can’t use it to call access().
UI.getCurrent() only returns a non-null value when the current thread owns the session lock. When called from a background thread, it returns null. Therefore, you can’t use it either to call access().
Whenever you’re planning to use server push, you have to get a hold of the UI instance while the user session is locked. This typically happens right before you start your background thread.
Below is an example of a button click listener that starts a background thread:
Source code
Java
button.addClickListener(clickEvent -> {
var ui = UI.getCurrent(); 1
taskExecutor.execute(() -> { 2
// Do your work here
ui.access(() -> {
// Update your UI here
});
});
});-
This is executed in an HTTP request thread. The user session is locked and
UI.getCurrent()returns the currentUI-instance. -
This is executed in the background thread.
UI.getCurrent()returnsnull, but theUIinstance is stored in a local variable.
|
Tip
|
This is the kind of bookkeeping signals remove for you. When you push state through a signal instead, you don’t capture the UI instance and you don’t call access() — see Pushing State with Signals.
|
Access Later
You probably often use server push in various types of event listeners and callbacks. A background job might inform you that it has finished processing.
In the following example, the user interface is updated in a callback after a background job has finished:
Source code
Java
var ui = UI.getCurrent();
myService.startBackgroundJob(() -> ui.access(() -> {
// Update your UI here when the job is finished
}));Another common use case is an event bus informing you of a new message.
In the following example, the user interface subscribes to an event bus, and updates the user interface whenever a new message arrives:
Source code
Java
var ui = UI.getCurrent();
var subscription = myEventBus.subscribe((message) -> ui.access(() -> {
// Update your UI here when a message has arrived
}));In cases like these, you should consider using UI.accessLater(), instead of UI.access().
UI.accessLater() exists in two versions: one that wraps a SerializableRunnable; and another that wraps a SerializableConsumer. It stores the UI instance, and runs the wrapped delegate inside a call to UI.access().
It also takes a second parameter, which is a detach handler. The detach handler is a Runnable that runs if the UI has been detached when UI.access() is called. The detach handler can be null if no special actions are needed.
Rewritten with accessLater(), the thread completion example becomes this:
Source code
Java
myService.startBackgroundJob(UI.getCurrent().accessLater(() -> {
// Update your UI here when the job is finished.
}, null));Likewise, the event listener becomes this:
Source code
Java
var subscription = myEventBus.subscribe(UI.getCurrent().accessLater((message) -> {
// Update your UI here when a message has arrived
}, null));Avoiding Memory Leaks
When you’re using server push to update the user interface when an event has occurred, you would typically subscribe a listener to some broadcaster or event bus. When you do this, be sure to unsubscribe when the UI is detached. Otherwise, you’ll have a memory leak that prevents your UI from being garbage collected. This is because the listener holds a reference to the UI instance.
Always subscribe when your view is attached to a UI, and unsubscribe when it’s detached. You can do this by overriding the Component.onAttach() method, like so:
Source code
Java
@Override
protected void onAttach(AttachEvent attachEvent) { 1
var ui = attachEvent.getUI(); 2
var subscription = myEventBus.subscribe(ui.accessLater((message) -> {
// Update your UI here when a message has arrived
}, null));
addDetachListener(detachEvent -> {
detachEvent.unregisterListener(); 3
subscription.unsubscribe(); 4
});
}-
Subscribe when the view is attached to a UI.
-
Get the
UIfrom theAttachEvent. -
Remove the detach listener itself, to prevent a memory leak in case the component is attached multiple times.
-
Unsubscribe when the view is detached from the UI.
|
Tip
| Binding a component to a signal avoids this bookkeeping entirely: the binding is active only while the component is attached and is removed automatically on detach. See Broadcasting to All Users for the signal-based version of this example. |
Avoiding Floods
Another risk you have to manage when updating the user interface in response to events is flooding the user interface with updates. This applies whether you push through signals or through UI.access() directly. As a rule of thumb, you should not push more than two to four times per second. Pushing more often than that can cause performance issues. Plus, there is a limit to how many updates the human brain is able to register per second.
When you know events are coming no faster than two to four events per second, you can push on every event. However, if they’re more frequent, you have to buffer events and update the user interface in batches — for signals, this means buffering before you write to the signal. This is quite easy to do if you’re using a Flux from Reactor. See the Consuming Reactive Streams documentation page for more information about this.
The buffering duration depends on the size of the UI update, and the network latency. In some applications, you may need to use a longer buffer duration. In others, a shorter one might work. You should try various durations to see what’s best for your application.
Avoiding Unnecessary Pushes
The UI.access() method updates the user interface, asynchronously. The update operation is not executed immediately, but added to a queue and executed at some time later. If this is combined with regular event-driven updates in the HTTP request thread, you may have a situation in which the user interface is updated out-of-order.
To understand better, look at this example:
Source code
Java
var button = new Button("Test Me", event -> {
UI.getCurrent().access(() -> {
add(new Div("This <div> is added from within a call to UI.access()"));
});
add(new Div("This <div> is added from an event listener"));
});
add(button);If you were to click the button, the user interface would look like this:
Source code
This <div> is added from an event listener
This <div> is added from within a call to UI.access()In this particular case, the call to UI.access() would not have been needed. Sometimes, you can deduce this by looking at the code. However, there are situations in which this isn’t obvious. You may have code that’s executed sometimes by the HTTP request thread, and other times by another thread. For this situation, you can check whether the current thread has locked the user session, like this:
Source code
Java
if (ui.getSession().hasLock()) {
// Update the UI without calling UI.access()
} else {
ui.access(() -> {
// Update the UI inside UI.access()
});
}