Events

CDI events globally provide an event BUS inside the application. They can be synchronous or asynchronous. To let you have an idea, here is what the code can look like:

@ApplicationScoped
public class LifecycleManager {
@Inject
private Event<Starting> startingEvent;

public void starting() {
final Starting event = new Starting();

startingEvent.fire(event);
startingEvent.fireAsync(event);
}
}

As both types of invocations are exclusive, what we can note here is that these snippets call fire() and fireAsync(). To be able to target all the observers, you need to invoke both. This means that the associated logic will be twice.

Without entering into the details that do not impact our performance, both cases share the same resolution mechanism:

  1. Resolve the observers based on the event type.
  2. Remove the observers not matching the fire type (asynchronous or synchronous).
  3. Sort the observers by priority.
  4. Handle the invocations.

The difference between synchronous and asynchronous cases is point 4. In the synchronous case, it just means, invoke the observers, whereas in the asynchronous case, it means, call asynchronously and return CompletionStage representing all the invocation results.

The parts impacting the performance are the resolution of the observers and the invocation, which can require some bean resolution.

We already saw bean resolution, so let's dig into the observer resolution here. Indeed, the implementation is specific to the vendor you are using. But, as it is impossible to use static analysis to implement this part, the resolution is done at runtime with a cache per event type. Note that the caching depends a lot on the implementation. Most will only cache raw type events.

This concretely means that the invocation without generics, as shown in the following code, will be way faster than the invocation that implements generics and enforces the CDI container to do some more resolution:

event.fire(new MyEvent());

In terms of the code, and to let you compare it with the previous example, the code with generics would be exactly the same except the event would be parameterized:

event.fire(new MyEvent<String>());

Then, once you have the potential set of observers, you need to reduce the set based on the qualifiers that the caller configures for the event. This also implies some reflection, more or less cached, depending on the implementation.

Finally, some runtime checks are enforced by the set of tests that the vendors have to pass so that we can claim to be compliant with the specifications.

All these steps are more or less optimized by vendors depending on the cases they may have received complaints about. But in all of them, you can end up on code paths where everything is done at runtime for the firing of each event, which can be a pain in terms of the performance.