Diagnosing Lost UI State in Vaadin Flow After Client‑Side Navigation
When a Vaadin Flow app navigates client‑side, the Java model may stay intact while the browser view disappears. This guide shows how to detect, diagnose, and fix the state‑sync problem, with concrete checks and code snippets.
21 Oct 2025, 00:34 UTC

Recognizable Condition
After a client‑side navigation (e.g., a RouterLink click), the page shows the correct view, but the component values that were set on the server side (e.g., a TextField value or a Grid selection) are missing or reset. The Java model remains unchanged, but the DOM does not reflect it.
Primary Cause
The server‑side UI state synchronization that normally runs during a navigation request is skipped because the client‑side router performs the navigation without a full HTTP round‑trip. The ClientConnector#enter hook that pushes the updated state to the browser is not invoked, leaving the client view stale.
Diagnostic Table
| Check | What to look for | Result indicates |
|---|---|---|
| 1 – Router registration | Target view annotated with @Route | Missing: server never receives navigation request |
| 2 – Browser console | vaadin:// WebSocket messages, no JS errors | Missing messages: broken sync channel |
| 3 – Server log | UIState sync entry after navigation | Absent: state not pushed |
Ordered Checks
- Verify @Route Annotation
Ensure every view that can be navigated to from the client side has a@Route("path")annotation. If a view is missing this, Vaadin’s server‑side router never processes the navigation, so no state sync occurs.import com.vaadin.flow.router.Route; @Route("dashboard") public class DashboardView extends VerticalLayout { ... } - Inspect Browser Console
Open DevTools (F12) and check for errors. Look for messages like:vaadin://sync: failed to send state delta
If you see a WebSocket error or a missingvaadin://message, the client cannot reach the server. - Check Server Side Logging
Add a simple debug listener to capture UI state sync events:
After a client‑side navigation, look for a log entry similar to:UI.getCurrent().addUIInitListener(ui -> ui.getPage().executeJs("console.log('UI initialized')"));UIState: pushing 3 connectors
If absent, the sync hook was not called. - Validate WebSocket Connection
In the Network tab, filter byWebSocketand confirm thevaadin://channel is open and exchanging frames. A closed or error‑state socket prevents state pushes.
Fixes Tied to Findings
- Missing @Route
Add the annotation to the view class. This ensures the server processes navigation and triggersClientConnector#enter. - Broken WebSocket
Verify that the application is served from the same origin and that firewalls or proxies allow WebSocket traffic. If necessary, configure the server to use avaadin://fallback or enablevaadin.disableWebSocketto force HTTP polling. - State Sync Not Triggered
Force a manual sync after a client‑side navigation by adding a listener inonAttachof the view:@Override public void onAttach(AttachEvent attachEvent) { // Force a full state push UI.getCurrent().getPage().executeJs( "window.vaadinApi.updateState()" ); } - Preserve Router State
On recent Vaadin versions, the router can preserve UI state across client‑side navigation:
This tells the router to keep the server UI alive and push any pending state changes.Router router = UI.getCurrent().getRouter(); router.setPreserveState(true);
Escalation Criteria
If after applying the above fixes the UI state still does not appear, consider:
- Custom
ClientConnectorsubclasses that overrideonStateChangedwithout callingsuper. - Third‑party add‑ons that modify the Vaadin client bundle or interfere with the WebSocket layer.
- High‑traffic production environments where frequent
entercalls may overload the server; in this case, enablevaadin.debugto throttle syncs.
Gather the following before raising a ticket:
- Server console logs showing the absence of UIState sync entries.
- Browser console with WebSocket frames and any error messages.
- Configuration files (e.g.,
application.properties) showing router settings.
Verification Checklist
- Open the app in a browser and navigate to a view via
RouterLink. - Set a value on a component in the target view.
- Navigate away using another
RouterLinkand return. - Confirm the component still shows the value set earlier.
- Check the server log for a line like
UIState: pushing…after the return navigation. - In the browser console, see a
vaadin://message indicating a state delta was sent.
Limitations & Practical Notes
- Forcing a full state sync on every navigation can increase server load; use sparingly in high‑traffic apps.
- Enabling
router.setPreserveState(true)may interfere with deep‑link handling; test all navigation paths before production. - Custom
ClientConnectorimplementations must callsuper.onStateChanged()to avoid breaking the sync chain. - Some proxies strip WebSocket headers; ensure
UpgradeandConnectionheaders are preserved.
Conclusion
Loss of UI state after client‑side navigation in Vaadin Flow usually stems from a missing @Route annotation, a broken WebSocket channel, or an overridden sync hook. By following the ordered checks, applying the targeted fixes, and verifying the state sync, you can restore consistent UI behavior across all navigation paths.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.