Using Vaadin Flow's @Push for Real-Time Updates: A Practical Guide
Vaadin Flow's @Push lets the server update the UI instantly, but silent transport fallbacks and CSP rules can break it. Here is how to enable it safely and verify the connection.
24 Jun 2026, 01:27 UTC

You have a Vaadin dashboard that shows inventory, sensor readings, or chat messages. With the default request-response model, the browser has to ask the server for new data every few seconds. That polling cycle wastes bandwidth, adds latency, and makes the UI feel stale. Vaadin Flow's @Push annotation removes the polling loop by letting the server initiate updates over a persistent connection. The useful takeaway: enabling @Push is straightforward, but you must verify that the WebSocket transport actually works in your environment, because silent fallbacks and security headers can break it without obvious errors.
Why polling falls short
In a standard Vaadin Flow application, the client sends a request and the server responds. If you want fresh data, the client must keep asking. For a dashboard with twenty users polling every two seconds, that is ten requests per second just to check for changes, even when nothing has changed. The cost scales linearly with users and poll frequency.
@Push changes the model. Instead of the client repeatedly asking, the server holds a connection open and sends updates when state changes. Under the hood, Vaadin Flow can use WebSocket or Server-Sent Events (SSE). WebSocket provides full-duplex communication; SSE is a simpler unidirectional fallback. The goal is the same: the UI updates when the server says so, not when the browser next happens to poll.
How @Push works in practice
You enable push by adding @Push to your UI class or to a @Route-annotated view. In a Spring Boot application, the necessary infrastructure is often already present, but depending on your Vaadin version and stack, you may need to configure a PushConfiguration bean and select a push provider such as Vert.x or Spring WebSocket. Treat the exact provider setup as version-specific and verify it against the documentation for your release.
The critical rule is thread safety. Vaadin Flow UI components are not thread-safe. When a background thread—say, a message listener or a scheduled task—has new data, it cannot touch the UI directly. It must call UI.access(), which locks the session and marshals the update onto the correct UI thread.
A minimal live-counter example
Below is a typical pattern for a view that updates every second from a background thread. This is an illustrative example based on documented Vaadin Flow APIs; adjust executor lifecycle management for production use.
@Route("live-counter")
@Push
public class LiveCounterView extends VerticalLayout {
private final Span counter = new Span("0");
private int value = 0;
public LiveCounterView() {
add(new H1("Live Counter"), counter);
UI ui = UI.getCurrent();
ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
value++;
ui.access(() -> counter.setText(String.valueOf(value)));
}, 1, 1, TimeUnit.SECONDS);
}
}
Two details matter here. First, @Push on the route tells Vaadin to establish a push transport for this view. Second, ui.access() wraps the component update. Without that wrapper, you risk race conditions and inconsistent session state. If you open this view in two browser tabs, both should show the same counter value incrementing in near-lockstep, because each tab holds its own push connection to the same server-side UI instance.
Trade-offs and limitations
Server push is not free. The table below contrasts it with basic polling.
| Approach | Latency | Connection model | Typical risk |
|---|---|---|---|
| Client polling | Poll interval | Short-lived HTTP requests | Wasted requests when idle |
| @Push (WebSocket) | Near-instant | Persistent connection | Proxy/firewall blocking; UI thread saturation |
UI thread pressure. If your background thread fires hundreds of updates per second, each UI.access() call queues work on the UI thread. The queue can grow faster than it drains, causing lag or memory pressure. Throttle or batch updates when the source is chatty.
Transport fallback. WebSocket requires support from the servlet container, proxies, and load balancers. If any layer blocks WebSocket, Vaadin may fall back to long-polling or another transport. That fallback can be silent: the application still works, but you lose the low-latency benefit and may see different behavior in production than in development.
Content Security Policy. Strict CSP headers can prevent WebSocket connections. If your connect-src or frame-src directives do not allow the push endpoint, the browser will refuse the connection. This is a common source of "works locally, fails in staging" issues.
Verifying that push is actually working
Because fallbacks are silent, verification is part of the implementation, not an afterthought.
- Check the Network tab. Open browser DevTools, reload the view, and look for a WebSocket connection. The exact endpoint path depends on your servlet mapping and Vaadin version; common patterns include
/vaadinServlet/pushor/push. Confirm the status is101 Switching Protocols, not a failed or downgraded request. - Open multiple tabs. Load the same view in two tabs. The counter should update in both without a manual refresh. If one tab lags or requires a reload, the push connection for that tab may have dropped.
- Inspect the console. Look for WebSocket errors or CSP violation messages. A blocked connection often appears as a console error before the UI stops updating.
- Test through your real proxy. If you deploy behind a load balancer or API gateway, test through that layer. Local development often bypasses the network policy that will govern production.
An actionable checklist
- Apply
@Pushonly to views that genuinely need server-initiated updates; leave the rest on standard request-response to reduce connection overhead. - Always wrap background-thread UI changes in
UI.access(). - Batch or throttle high-frequency updates so the UI thread is not overwhelmed.
- Verify the WebSocket handshake in DevTools, and confirm your CSP and proxy configuration allow the push endpoint.
- Test in an environment that mirrors your production network topology before release.
@Push turns Vaadin Flow from a request-response framework into a real-time one. The annotation itself is simple; the engineering decision is whether your infrastructure, security policy, and update frequency justify a persistent connection. Verify the transport, respect the UI thread, and push becomes a reliable tool rather than a source of hard-to-debug production surprises.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.