Beans may produce and consume events. This facility allows beans to interact in a completely decoupled fashion, with no compile-time dependency between the interacting beans. Most importantly, it allows stateful beans in one architectural tier of the application to synchronize their internal state with state changes that occur in a different tier.
An event comprises:
-
A Java object - the event object
-
A set of instances of qualifier types - the event qualifiers
The event object acts as a payload, to propagate state from producer to consumer. The event qualifiers act as topic selectors, allowing the consumer to narrow the set of events it observes.
An observer method acts as event consumer, observing events of a specific type - the observed event type - with a specific set of qualifiers - the observed event qualifiers. An observer method will be notified of an event if the event object is assignable to the observed event type, and if the set of observed event qualifiers is a subset of all the event qualifiers of the event.
An event object is an instance of a concrete Java class with no unresolvable type variables. The event types of the event include all superclasses and interfaces of the runtime class of the event object.
An event type may not contain an unresolvable type variable. A wildcard type is not considered an unresolvable type variable.
An event qualifier type is just an ordinary qualifier type as specified in [defining_qualifier_types], typically defined as @Target({METHOD, FIELD, PARAMETER, TYPE})
or @Target({FIELD, PARAMETER})
.
Every event has the qualifier @jakarta.enterprise.inject.Any
, even if it does not explicitly declare this qualifier.
Any Java type may be an observed event type.
Beans fire events via an instance of the jakarta.enterprise.event.Event
interface, which may be injected:
@Inject Event<LoggedInEvent> loggedInEvent;
Any combination of qualifiers may be specified at the injection point:
@Inject @Admin Event<LoggedInEvent> adminLoggedInEvent;
Or the application may specify qualifiers dynamically:
@Inject Event<LoggedInEvent> loggedInEvent;
...
LoggedInEvent event = new LoggedInEvent(user);
if ( user.isAdmin() ) {
loggedInEvent.select( new AdminQualifier() ).fire(event);
}
else {
loggedInEvent.fire(event);
loggedInEvent.fireAsync(event);
}
In this example, the event sometimes has the qualifier @Admin
, depending upon the value of user.isAdmin()
.
It is first fired synchronously with fire()
then asynchronously with fireAsync()
.
The method fire()
accepts an event object:
public void login() {
...
loggedInEvent.fire( new LoggedInEvent(user) );
}
Event fired with the fire()
method is fired synchronously.
All the resolved synchronous observers (as defined in Observer resolution) are called in the thread in which fire()
was called.
A synchronous observer notification blocks the calling thread until it completes.
Events may also be fired asynchronously using one of the methods fireAsync()
@Inject Event<LoggedInEvent> loggedInEvent;
public void login() {
...
loggedInEvent.fireAsync( new LoggedInEvent(user) );
}
Event fired with the fireAsync()
method is fired asynchronously.
All the resolved asynchronous observers (as defined in Observer resolution) are called in one or more different threads.
Method fireAsync()
returns immediately.
The Event
interface provides a method for firing events with a specified combination of type and qualifiers:
public interface Event<T> {
public void fire(T event);
public <U extends T> CompletionStage<U> fireAsync(U event);
public <U extends T> CompletionStage<U> fireAsync(U event, NotificationOptions options);
public Event<T> select(Annotation... qualifiers);
public <U extends T> Event<U> select(Class<U> subtype, Annotation... qualifiers);
public <U extends T> Event<U> select(TypeLiteral<U> subtype, Annotation... qualifiers);
}
For an injected Event
:
-
the specified type is the type parameter specified at the injection point, and
-
the specified qualifiers are the qualifiers specified at the injection point.
For example, this injected Event
has specified type LoggedInEvent
:
@Inject Event<LoggedInEvent> any;
The select()
method returns a child Event
for a given specified type and additional specified qualifiers.
If no specified type is given, the specified type is the same as the parent.
For example, this child Event
has required type AdminLoggedInEvent
and additional specified qualifier @Admin
:
Event<AdminLoggedInEvent> admin = any.select(
AdminLoggedInEvent.class,
new AdminQualifier() );
If the specified type contains a type variable, an IllegalArgumentException
is thrown.
If two instances of the same non repeating qualifier type are passed to select()
, an IllegalArgumentException
is thrown.
If an instance of an annotation that is not a qualifier type is passed to select()
, an IllegalArgumentException
is thrown.
The methods fire()
and fireAsync()
fire an event with the specified qualifiers and notify observers, as defined by Observer notification.
If the container is unable to resolve the parameterized type of the event object, it uses the specified type to infer the parameterized type of the event types.
The method fireAsync()
may be called with a NotificationOptions
object to configure the observer methods notification, e.g. to specify an Executor
object to be used for asynchronous delivery.
The container is permitted to define other non-portable notification options.
The following elements are container specific:
-
the default
Executor
used by the container whenfireAsync()
is called without specifying anExecutor
, -
the
CompletionStage
returned byfireAsync
methods, and -
all dependent stages of this initial
CompletionStage
.
If the runtime type of the event object contains an unresolvable type variable, an IllegalArgumentException
is thrown.
If the runtime type of the event object is assignable to the type of a container lifecycle event, an IllegalArgumentException
is thrown.
The container must provide a built-in bean with:
-
Event<X>
in its set of bean types, for every Java typeX
that does not contain a type variable, -
every event qualifier type in its set of qualifier types,
-
scope
@Dependent
, -
no bean name, and
-
an implementation provided automatically by the container.
If an injection point of raw type Event
is defined, the container automatically detects the problem and treats it as a definition error.
The built-in implementation must be a passivation capable dependency, as defined in [passivation_capable_dependency].
The process of matching an event to its observer methods is called observer resolution. The container considers event type and qualifiers when resolving observers.
Observer resolution usually occurs at runtime.
An event is delivered to an observer method if:
-
The observer method belongs to an enabled bean.
-
An event type is assignable to the observed event type, taking type parameters into consideration.
-
The observer method has no event qualifiers or has a subset of the event qualifiers. An observer method has an event qualifier if it has an observed event qualifier with (a) the same type and (b) the same annotation member value for each member which is not annotated
@jakarta.enterprise.util.Nonbinding
. -
Either the event is not a container lifecycle event, as defined in [init_events], or the observer method belongs to an extension.
-
The event is fired synchronously and the observer is a synchronous observer as defined in Declaring an observer method.
-
The event is fired asynchronously and the observer is an asynchronous observer as defined in Declaring an observer method.
If the runtime type of the event object contains an unresolvable type variable, the container must throw an IllegalArgumentException
.
For a custom implementation of the ObserverMethod
interface defined in [observer_method], the container must call getObservedType()
and getObservedQualifiers()
to determine the observed event type and qualifiers, and isAsync()
to determine whether the observer is asynchronous or synchronous.
An event type is considered assignable to a type variable if the event type is assignable to the upper bound, if any.
A raw event type is considered assignable to a parameterized observed event type if the raw types are identical and all type parameters of the required type are either unbounded type variables or java.lang.Object
.
A parameterized event type is considered assignable to a raw observed event type if the raw types are identical.
A parameterized event type is considered assignable to a parameterized observed event type if they have identical raw type and for each parameter:
-
the observed event type parameter is an actual type with identical raw type to the event type parameter, and, if the type is parameterized, the event type parameter is assignable to the observed event type parameter according to these rules, or
-
the observed event type parameter is a wildcard and the event type parameter is assignable to the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or
-
the observed event type parameter is a type variable and the event type parameter is assignable to the upper bound, if any, of the type variable.
As usual, the qualifier type may have annotation members:
@Qualifier
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Role {
String value();
}
Consider the following event:
@Inject Event<LoggedInEvent> loggedInEvent;
...
public void login() {
final User user = ...;
loggedInEvent.select(new RoleQualifier() { public String value() { return user.getRole(); } }).fire(new LoggedInEvent(user));
}
Where RoleQualifier
is an implementation of the qualifier type Role
:
public abstract class RoleQualifier
extends AnnotationLiteral<Role>
implements Role {}
Then the following observer method will always be notified of the event:
public void afterLogin(@Observes LoggedInEvent event) { ... }
Whereas this observer method may or may not be notified, depending upon the value of user.getRole()
:
public void afterAdminLogin(@Observes @Role("admin") LoggedInEvent event) { ... }
As usual, the container uses equals()
to compare event qualifier type member values.
An event parameter may have multiple qualifiers.
public void afterDocumentUpdatedByAdmin(@Observes @Updated @ByAdmin Document doc) { ... }
Then this observer method will be notified if the set of observer qualifiers is a subset of the fired event’s qualifiers or an empty set:
@Inject Event<Document> documentEvent;
...
documentEvent.select(new UpdatedQualifier(), new ByAdminQualifier(), new ClarificationQualifier()).fire(document);
In the above example the event is fired with @ByAdmin
, @Updated
, and @Clarification
qualifiers. The observer qualifiers are @Updated
and @ByAdmin
.
Observer qualifiers therefore form a subset of event qualifiers and the observer will be notified.
Other, less specific, observers will also be notified of this event:
public void afterDocumentUpdated(@Observes @Updated Document doc) { ... }
public void afterDocumentEvent(@Observes Document doc) { ... }
On the other hand, following observer will not be notified as slightly different behaviour applies to observers with @Default
qualifier:
public void afterDocumentDefaultEvent(@Observes @Default Document doc) { ... }
Such observer will only be notified for events having either no qualifiers or only @Default
qualifier:
@Inject Event<Document> documentEvent;
@Inject @Default Event<Document> documentDefaultEvent;
...
documentEvent.fire(document);
documentDefaultEvent.fire(document);
An observer method allows the application to receive and respond to event notifications.
An observer method is a non-abstract method of a managed bean class. An observer method may be either static or non-static.
There may be arbitrarily many observer methods with the same event parameter type and qualifiers.
A bean may declare multiple observer methods.
Each observer method must have exactly one event parameter, of the same type as the event type it observes. When searching for observer methods for an event, the container considers the type and qualifiers of the event parameter.
If the event parameter does not explicitly declare any qualifier, the observer method observes events with no qualifier.
The event parameter type may contain a type variable or wildcard.
The event parameter may be an array type whose component type contains a type variable or a wildcard.
Modifications made to the event parameter in an observer method are propagated to following observers. The container is not required to guarantee a consistent state for an event parameter modified by asynchronous observers.
An observer method may be declared by annotating a parameter @jakarta.enterprise.event.Observes
or @jakarta.enterprise.event.ObservesAsync
of a default-access, public, protected or private method.
That parameter is the event parameter.
The declared type of the parameter is the observed event type.
If @Observes
is used the observer method is a synchronous observer method.
If @ObservesAsync
is used the observer method is an asynchronous observer method.
public void afterLogin(@Observes LoggedInEvent event) { ... }
public void asyncAfterLogin(@ObservesAsync LoggedInEvent event) { ... }
If a method has more than one parameter annotated @Observes
or @ObservesAsync
, the container automatically detects the problem and treats it as a definition error.
If a method has a parameter annotated @Observes
and @ObservesAsync
, the container automatically detects the problem and treats it as a definition error.
Observed event qualifiers may be declared by annotating the event parameter:
public void afterLogin(@Observes @Admin LoggedInEvent event) { ... }
If an observer method is annotated @Produces
or @Inject
or has a parameter annotated @Disposes
, the container automatically detects the problem and treats it as a definition error.
Interceptors may not declare observer methods.
If an interceptor has a method with a parameter annotated @Observes
or @ObservesAsync
, the container automatically detects the problem and treats it as a definition error.
In addition to the event parameter, observer methods may declare additional parameters, which may declare qualifiers. These additional parameters are injection points.
public void afterLogin(@Observes LoggedInEvent event, @Manager User user, Logger log) { ... }
The interface jakarta.enterprise.inject.spi.EventMetadata
provides access to metadata about an observed event.
public interface EventMetadata {
public Set<Annotation> getQualifiers();
public InjectionPoint getInjectionPoint();
public Type getType();
}
-
getQualifiers()
returns the set of qualifiers with which the event was fired. -
getInjectionPoint()
returns theInjectionPoint
from which this event payload was fired, ornull
if it was fired fromBeanManager.getEvent()
. -
getType()
returns the type representing runtime class of the event object with type variables resolved.
The container must provide a bean with scope @Dependent
, bean type EventMetadata
and qualifier @Default
, allowing observer methods to obtain information about the events they observe.
If an injection point of type EventMetadata
and qualifier @Default
which is not a parameter of an observer method exists, the container automatically detects the problem and treats it as a definition error.
public void afterLogin(@Observes LoggedInEvent event, EventMetadata metadata) { ... }
A conditional observer method is an observer method which is notified of an event only if an instance of the bean that defines the observer method already exists in the current context.
A conditional observer method may be declared by specifying notifyObserver=IF_EXISTS
.
public void refreshOnDocumentUpdate(@Observes(notifyObserver=IF_EXISTS) @Updated Document doc) { ... }
public void asyncRefreshOnDocumentUpdate(@ObservesAsync(notifyObserver=IF_EXISTS) @Updated Document doc) { ... }
Beans with scope @Dependent
may not have conditional observer methods.
If a bean with scope @Dependent
has an observer method declared notifyObserver=IF_EXISTS
, the container automatically detects the problem and treats it as a definition error.
The enumeration jakarta.enterprise.event.Reception
identifies the possible values of notifyObserver
:
public enum Reception { IF_EXISTS, ALWAYS }
Transactional observer methods are observer methods which receive event notifications during the before or after completion phase of the transaction in which the event was fired. If no transaction is in progress when the event is fired, they are notified at the same time as other observers.
If the transaction is in progress, but jakarta.transaction.Synchronization
callback cannot be registered due to the transaction being already marked for rollback or in state where jakarta.transaction.Synchronization
callbacks cannot be registered, the before completion, after completion and after failure observer methods are notified at the same time as other observers, but after_success observer methods get skipped.
-
A before completion observer method is called during the before completion phase of the transaction.
-
An after completion observer method is called during the after completion phase of the transaction.
-
An after success observer method is called during the after completion phase of the transaction, only when the transaction completes successfully.
-
An after failure observer method is called during the after completion phase of the transaction, only when the transaction fails.
The enumeration jakarta.enterprise.event.TransactionPhase
identifies the kind of transactional observer method:
public enum TransactionPhase {
IN_PROGRESS,
BEFORE_COMPLETION,
AFTER_COMPLETION,
AFTER_FAILURE,
AFTER_SUCCESS
}
A transactional observer method may be declared by specifying any value other than IN_PROGRESS
for during
:
void onDocumentUpdate(@Observes(during=AFTER_SUCCESS) @Updated Document doc) { ... }
Asynchronous observer cannot be declared transactional.
When an event is fired by the application, the container must:
-
determine the observer methods for that event according to the rules of observer resolution defined by Observer resolution, then,
-
for each observer method, either invoke the observer method immediately, or register the observer method for later invocation during the transaction completion phase, using a JTA
Synchronization
. -
honor the priority of observer methods as defined in Observer ordering.
The container calls observer methods as defined in [observers_method_invocation].
-
If the observer method is a transactional observer method and there is currently a JTA transaction in progress, the container calls the observer method during the appropriate transaction completion phase.
-
If there is no context active for the scope to which the bean declaring the observer method belongs, then the observer method should not be called.
-
Otherwise, the container calls the observer immediately.
Any observer method called before completion of a transaction may call setRollbackOnly()
to force a transaction rollback.
An observer method may not directly initiate, commit or rollback JTA transactions.
Observer methods may throw exceptions:
-
If the observer method is a transactional observer method, any exception is caught and logged by the container.
-
If the observer method is asynchronous, the exception aborts processing of the observer but not of the event. Exception management during an asynchronous event is defined in Handling multiple exceptions thrown during an asynchronous event.
-
Otherwise, the exception aborts processing of the event. No other observer methods of that event will be called. The
BeanManager.getEvent()
orEvent.fire()
method rethrows the exception. If the exception is a checked exception, it is wrapped and rethrown as an (unchecked)ObserverException
.
For a custom implementation of the ObserverMethod
interface defined in [observer_method], the container must call getTransactionPhase()
to determine if the observer method is transactional observer method, and notify()
which accepts jakarta.enterprise.inject.spi.EventContext
to invoke the method.
If an event is asynchronous, and an exception is thrown by any of its notified observers, the CompletionStage
returned by fireAsync
will complete exceptionally with java.util.concurrent.CompletionException
exception.
CompletionException
contains all exceptions thrown by observers as suppressed exceptions.
They can be accessed as an array of Throwable
with the getSuppressed
method.
It can be handled with one of the CompletionStage
methods related to exceptions:
myEvent.fireAsync(anEventObject)
.handle((ok, ex) -> {
if (ok != null) {
return ok;
} else {
for (Throwable t : ex.getSuppressed()) {
...
}
...
} });
If no exception is thrown by observers then the resulting CompletionStage
is completed normally with the event object.
Before the actual observer notification, the container determines an order in which the observer methods for a certain event are invoked.
The priority of an observer method may be declared by annotating the event parameter with @Priority
annotation.
If a @Priority
annotation is declared on an event parameter of an asynchronous observer method, non-portable behavior results.
If no @Priority
annotation is specified, the default priority jakarta.interceptor.Interceptor.Priority.APPLICATION + 500
is assumed.
Observers with smaller priority values are called first.
void afterLogin(@Observes @Priority(jakarta.interceptor.Interceptor.Priority.APPLICATION) LoggedInEvent event) { ... }
The order of more than one observer with the same priority is undefined and the observer methods are notified therefore in a non predictable order.
The transaction context and lifecycle contexts active when an observer method is invoked depend upon what kind of observer method it is.
-
If the observer method is asynchronous, it is called in a new lifecycle contexts and a new transaction context. As specified in [builtin_contexts], contexts associated with built-in normal scope don’t propagate across asynchronous observers.
-
If the observer method is a before completion transactional observer method, it is called within the context of the transaction that is about to complete and with the same lifecycle contexts.
-
Otherwise, if the observer method is any other kind of transactional observer method, it is called in an unspecified transaction context, but with the same lifecycle contexts as the transaction that just completed.
-
Otherwise, the observer method is called in the same transaction context and lifecycle contexts as the invocation of
Event.fire()
orBeanManager.getEvent()
.