Service Event Bus
- Registering Listeners
- Session Lock Events
- RPC Invocation Events
- Data Provider Query Events
- Other Framework Events
- Firing Your Own Events
- Deprecated Listener Interfaces
Every VaadinService has an event bus through which the framework reports what it’s doing, and through which an application can fire service-wide events of its own.
The events the framework fires cover the machinery that a request passes through: the session lock that serializes all server-side work for a session, the individual client-to-server RPC invocations handled while that lock is held, and the data provider queries those invocations trigger.
Listening to them is useful for performance monitoring, distributed tracing, and diagnosing lock contention — without modifying application logic.
The bus is reached with VaadinService.getEventBus(), and a listener is added for one event type:
Source code
Java
Registration registration = service.getEventBus()
.addListener(SessionLockAcquiredEvent.class,
event -> handleLockAcquired(event));The returned Registration removes the listener again with remove().
A few properties of the bus are worth knowing:
-
Events are dispatched by their exact runtime type. A listener registered for a supertype — for instance
AbstractDataFetchEvent— is never notified; register for each concrete event type you want. -
The bus is thread-safe. Listeners can be added and removed while events are being fired from other request threads. Requests belonging to different sessions are handled concurrently, so a listener must expect events from several sessions on several threads at once.
-
An exception thrown by a listener is logged, and the remaining listeners are notified regardless, so that one misbehaving listener can neither disrupt the framework nor hide the event from other listeners.
-
Listeners run on the request or access thread, directly around the operation they report. Implementations must be fast and non-blocking.
Registering Listeners
Listeners are typically added from a VaadinServiceInitListener, which receives the service before it starts handling requests.
How that init listener itself is discovered depends on the project type:
-
In Spring and CDI projects, annotate the class with
@Component(Spring) or make it a managed bean (CDI); it’s then registered automatically. -
In plain Java projects, register it through the Java Service Provider Interface by listing its fully qualified class name in
META-INF/services/com.vaadin.flow.server.VaadinServiceInitListener.
See Service Init Listener for the full details of each approach.
Session Lock Events
Because all of a session’s server-side work is serialized behind a single lock, the time a thread spends blocked acquiring that lock (the wait time) and the time it then holds it (the hold time) are key performance signals. Three events expose both:
| Event | When It’s Fired |
|---|---|
| Immediately before the thread attempts to acquire the lock. |
| Immediately after the lock has been acquired. |
| Immediately after the outermost hold has been released (the hold count reached zero). |
All three carry the VaadinService the lock belongs to, through getService().
The same lock instance protects a session whether it’s acquired by the framework while handling a request, or through VaadinSession.lock() — for example from UI.access().
Events are fired for the outermost acquisition only; reentrant re-locks aren’t reported.
For a single lock-hold, all three are fired on the same thread, in the order requested → acquired → released.
This makes a ThreadLocal a natural place to record timing, as shown in the following example:
Source code
SessionLockMetricsInitListener.java
When several listeners are registered, the requested and acquired events are delivered in registration order, while the released event is delivered in reverse registration order.
Listeners therefore nest like try/finally blocks: a listener registered later sees the release before one registered earlier does.
Listeners registered after a session’s lock has already been created are still honored.
RPC Invocation Events
A single client request typically carries several RPC invocations — a DOM event, a synchronized property update, a @ClientCallable or template event handler, a server-side navigation, a return channel message, and so on.
One event of each type is fired per invocation, which is useful for emitting a tracing span that shows exactly which invocation consumes the time spent holding the session lock during a request.
| Event | When It’s Fired |
|---|---|
| Immediately before an invocation is handled. |
| When handling an invocation threw, before the ended event.
The throwable is available from |
| Once an invocation has been handled, whether it completed normally or threw. |
All three expose the same details about the invocation:
| Method | Description |
|---|---|
| The |
| The protocol-level invocation type, such as |
| The id of the targeted |
| A human-readable identifier — the DOM event name, the invoked method name, the navigation location, and so on — or |
For one invocation, the started event, the optional failed event, and the ended event are fired on the same thread, in that order.
The ended event is always fired after the started one, regardless of outcome, so timing state can again be kept in a ThreadLocal.
Within one request the events don’t nest: those of one invocation are all fired before those of the next.
Being reported doesn’t mean the invocation had an effect: an RPC targeting a node that’s detached, disabled, or inert is reported and only then discarded unhandled.
Data Provider Query Events
Every fetch and count query a data-bound component issues is reported on the bus, so that slow backend queries can be measured where they happen instead of being inferred from overall request timings.
| Event | When It’s Fired |
|---|---|
| Before a page of items is requested from a data provider. |
| When the fetch threw, before the ended event.
The throwable is available from |
| Once the items have been loaded and consumed, or the fetch threw.
|
| Before a count query is issued. |
| When the count query threw, before the ended event. |
| Once the count query has returned or thrown.
|
The events describe the query and where it came from:
| Method | Description |
|---|---|
| The |
| An |
| The index of the first item requested, and how many were requested. Fetch events only. |
| Whether the query carried a filter, which distinguishes, for example, a Combo Box search from its initial page load. |
Because a data provider may return a lazily evaluated Stream, the ended event is fired only after the returned items have been consumed, so the measured duration covers the backend round-trip rather than only the call that started it.
The started and ended events of a query are fired on the same thread, and the ended event is fired in reverse registration order, so listeners nest around the started one.
Queries made for a component that isn’t attached to a live UI aren’t reported.
|
Note
|
Data fetches triggered by push updates run on the executor given to DataCommunicator.enablePushUpdates(), so these events aren’t always fired on a request thread.
|
Other Framework Events
The service lifecycle events are fired through the same bus, and the dedicated addSessionInitListener(), addSessionDestroyListener(), addServiceDestroyListener(), and addUIInitListener() methods on VaadinService are thin wrappers that register on it.
Either style works: use the listener interface when it exists, or listen for SessionInitEvent, SessionDestroyEvent, ServiceDestroyEvent, and UIInitEvent directly on the bus.
Firing Your Own Events
Any component of an application that needs to notify service-wide listeners can define an event type of its own and fire it on the bus, instead of maintaining its own listener collection.
An event type only has to extend EventObject:
Source code
Java
public class MaintenanceModeEvent extends EventObject {
public MaintenanceModeEvent(VaadinService service) {
super(service);
}
}
// Elsewhere, to notify the listeners:
service.getEventBus().fireEvent(new MaintenanceModeEvent(service));Three firing methods are available:
| Method | Description |
|---|---|
| Notifies the listeners of the event type in registration order, logging an exception from a listener and continuing with the rest. |
| The same, but hands a listener that threw to the given error handler instead of logging it. |
| Notifies the listeners in reverse registration order. Use it for the closing half of a pair of events, so that listeners nest around the opening one. |
On a hot code path, hasListener(MaintenanceModeEvent.class) tells whether building the event is worth it at all.
Deprecated Listener Interfaces
SessionLockListener and RpcInvocationListener, together with the VaadinService.addSessionLockListener() and VaadinService.addRpcInvocationListener() methods that register them, are deprecated for removal.
They still work and are still delivered from the bus, but new code should listen for the events directly:
| Deprecated Callback | Event |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The SessionLockEvent and RpcInvocationEvent classes the callbacks receive are deprecated with them; the new events carry the same information.
970a4b45-bc87-4381-9ab9-7c3e34e97b26