Managing Vaadin Flow Views with Server‑Side Routing in Spring Boot
Learn how Vaadin Flow’s @Route annotation and programmatic navigation let you map views to URLs, pass parameters safely, and update the browser history without full page reloads—plus the trade‑offs to watch for in memory and clustered environments.
14 Oct 2025, 02:25 UTC

Problem: Navigating UI without full page reloads
When building a rich web application with Vaadin Flow you often need to move between screens (for example, from a list of users to a detail view) while keeping the current URL in sync and avoiding a full page refresh. Doing this manually with JavaScript or custom servlet mappings adds boilerplate and can break the framework’s built‑in state management.
Thesis: Vaadin Flow’s @Route annotation and programmatic navigation give you a type‑safe, server‑side way to map views to URLs and trigger navigation without leaving the framework.
1. Mapping a view to a URL with @Route
Vaadin treats each UI view as a plain Java class. By annotating the class with @Route("path") the framework automatically registers that class for the given URL path. No XML, no Java‑config routing file is required.
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("users")
public class UsersView extends Div {
public UsersView() {
setText("List of users");
// add UI components here (e.g., a Grid)
}
}
Place this file anywhere in your application’s source tree (e.g., src/main/java/com/example/ui/UsersView.java). When you start the Vaadin Spring Boot starter (see verification steps below) and navigate to http://localhost:8080/users, the framework instantiates UsersView and renders it.
2. Passing parameters safely
Dynamic segments in the URL are declared with a colon syntax. The framework injects the matching values into method parameters annotated with @Parameter. This gives you compile‑time checking of the parameter type.
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Parameter;
import com.vaadin.flow.router.Route;
@Route("user/:id")
public class UserDetailView extends VerticalLayout {
public UserDetailView(@Parameter("id") Long userId) {
add(new Span("Showing details for user ID: " + userId));
// load user data from a service and display it
}
}
When the browser requests http://localhost:8080/user/42, Vaadin calls the constructor with userId = 42. If the segment cannot be parsed as a Long, the framework returns a 404 error.
3. Triggering navigation programmatically
Sometimes navigation must happen in response to a button click or a service call. Vaadin provides the current UI instance through a static helper, allowing you to navigate without writing HTML anchors.
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.router.Route;
@Route("")
public class MainView extends VerticalLayout {
public MainView() {
Button goToUser = new Button("Go to user 42", e ->
UI.getCurrent().navigate(UserDetailView.class, 42)
);
add(goToUser);
}
}
The call to UI.getCurrent().navigate(UserDetailView.class, 42) does two things:
- It creates (or retrieves) an instance of
UserDetailView. - It updates the browser’s URL to
/user/42and pushes a state entry into the history API, so the back button works as expected.
4. Trade‑off: view instantiation and clustering
By default each navigation creates a new instance of the view class (prototype scope). This keeps the UI state isolated per navigation but can increase memory usage if views are heavyweight or users navigate frequently.
- Limitation: Heavy views (large component trees, cached data) consumed on every navigation may strain server memory.
- Practical check: Start the application, open Chrome DevTools → Memory → Take a heap snapshot, navigate repeatedly between two views, and observe the heap size. If it grows unbounded, consider scoping.
- Mitigation: Annotate the view with
@SpringComponent(singleton) or define a custom scope (e.g.,@UIScope) to reuse instances. Remember that singleton views must be thread‑safe or use UI‑local storage for per‑user state. - Clustering note: Vaadin stores view state in the HTTP session. In a clustered deployment you must enable sticky sessions or replicate the session (e.g., via Spring Session with Redis) so that a user routed to another node does not lose view state.
Actionable closing
Start with the simple @Route approach for most views; it gives you clean, type‑safe URLs and automatic history management. Monitor memory usage during load testing, and if you see growth, switch heavyweight views to a singleton or custom scope. In production clusters, configure session replication or sticky routing to preserve Vaadin UI state across nodes. With these steps you get the responsiveness of a single‑page application while staying fully within Vaadin Flow’s server‑side model.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.