QueryParametersChangeEvent receives empty query parameters during rapid sidebar navigation

When a user clicks multiple sidebar navigation items rapidly (before the current view finishes loading), View.QueryParametersChangeEvent fires with empty query parameters. The navId parameter is missing on the server side, even though the browser URL correctly shows ?navId=xxx.

This causes the view to display its system/default title instead of the navigation-configured custom title.

How to Reproduce

  1. Create a sidebar menu with RouterLink items, each having ?navId=<value> in the URL
  2. Click menu item A (URL: /view-a?navId=abc) — view loads correctly, navId=abc received
  3. Before A finishes loading, quickly click menu item B (/view-b?navId=def)
  4. Quickly click menu item C or back to A
  5. Result: QueryParametersChangeEvent.getQueryParameters().getSingleParameter("navId") returns Optional.empty() — even though browser URL has ?navId=abc

Code

Sidebar link creation (using JmixListMenu + RouterLink):

public class AppListMenu extends JmixListMenu {
    @Override
    protected RouterLink createMenuItemComponent(ListMenu.MenuItem menuItem) {
        RouterLink routerLink = super.createMenuItemComponent(menuItem);
        if (menuItem instanceof BadgeMenuItem badge) {
            // Setting href manually with navId
            routerLink.getElement().setAttribute("href", badge.getUrl());
            // URL example: /applicants?navId=abc123
        }
        return routerLink;
    }
}

Receiving view:

public abstract class AbstractNavigationListView<E> extends StandardListView<E> {
    private Navigation navigation;

    @Override
    public @NonNull String getPageTitle() {
        if (navigation != null) {
            return navigation.getShortName(); // custom navigation title
        }
        return super.getPageTitle(); // falls back to system title like "Monitoring2"
    }

    @Subscribe
    public void onQueryParametersChange(final QueryParametersChangeEvent event) {
        String crcId = event.getQueryParameters()
                .getSingleParameter("navId").orElse(null);
        // crcId is NULL during rapid clicking!
        setNavigation(navigationService.getByCRCId(crcId));
    }
}

What We Tried

  1. navId == null → return (skip) — prevents navigation from being set to null, but the view still loads without proper navigation context on first open
  2. Replaced setAttribute("href") with RouterLink.setRoute() + setQueryParameters() — same issue persists
  3. JS fallback (executeJs → read window.location.search) — async, causes title flickering
  4. Click debounce (300ms) on sidebar items — degrades UX
  5. BeforeEnterObserver — Jmix AI confirmed this uses the same router lifecycle, so same race condition applies

Expected Behavior

QueryParametersChangeEvent should always contain the query parameters matching the current browser URL, regardless of navigation speed.

Actual Behavior

During rapid navigation, the event arrives with empty/missing query parameters. The browser URL is correct, but the server-side event doesn’t reflect it.

Question

  1. Is this a known Vaadin router limitation with rapid client-side navigation?
  2. Is there a recommended pattern in Jmix for reliable query parameter handling that survives rapid navigation?
  3. Should we use a different mechanism entirely instead of ?navId query parameter (e.g., route path parameter)?

Environment: Jmix 2.7.4, Vaadin 24.9.9, Java 17

Hi!

Unfortunately, I was unable to reproduce the problem on my test project. I created a similar view configuration and tried to quickly press the router links:

@Route(value = "view-a", layout = MainView.class)
@ViewController(id = "ViewA")
@ViewDescriptor(path = "view-a.xml")
public class ViewA extends StandardView {

    private String title;

    @Override
    public String getPageTitle() {
        return !Strings.isNullOrEmpty(title) ? title : super.getPageTitle();
    }

    @Subscribe
    public void onQueryParametersChange(final QueryParametersChangeEvent event) {
        title = event.getQueryParameters()
                .getSingleParameter("title").orElse(null);
    }
}

I never got a null parameter value from the QueryParametersChangeEvent:

Could you create a small test project that reproduces the problem?

Best regards,
Dmitriy