Java EE Observer Design Pattern Example
This article is about a Java EE Observer Design Pattern Example. The observer pattern is one of the most widely used design patterns in programming. It is implemented in thejava.util package of Java SE 8 asObserver andObservable.
1. Introduction
By extending these classes, we can easily implement the observer pattern. But this article is not about that. We will focus on the Java EE implementation of the observer pattern.
In the observer pattern, an object that changes its state can inform other objects that a change in state has happened. The object that changes its state is the subject. The objects that receive the notification of the change of state are the observers. The observer pattern does this in a decoupled manner so that the subject does not know about the observers.
2. Tools and Requirements
The source in this is example is based on the Java EE Kickoff App. We will not go through the details of setting up the project, so it is recommended thatEclipse with WildFly and JBoss Tools Example be read before trying out the example. We’ll be using WildFly 14.x because it is a Java EE 8 full platform compatible implementation.
3. Java EE Observer Design Pattern Implementaiton
In Java EE, the subject and observer are decoupled. Many observers can receive notification about a change in the state of the subject. It relies on theObserves annotation. This annotation marks the observers and the subject uses theEvent class to create and fire events that observers listen for.
4. @Observes and Event
After downloading WildFly 14.x, we add it as one of our servers in Eclipse. We should have something like below:
Import thejava-ee-observer-pattern project in Eclipse. We will add some code in theAuthBacking class.AuthBacking.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | packageorg.example.kickoff.view.auth;...snipped...publicabstractclassAuthBacking { ...snipped... @Inject privateEvent<User> userLoginEvent; ...snipped... protectedvoidauthenticate(AuthenticationParameters parameters)throwsIOException { AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters); if(status == SEND_FAILURE) { addGlobalError("auth.message.error.failure"); validationFailed(); } elseif(status == SEND_CONTINUE) { responseComplete();// Prevent JSF from rendering a response so authentication mechanism can continue. } elseif(activeUser.hasGroup(ADMIN)) { userLoginEvent.fire(user); redirect("admin/users"); } elseif(activeUser.hasGroup(USER)) { userLoginEvent.fire(user); redirect("user/profile"); } else{ redirect(""); } } ...snipped...} |
Parts of the code have been snipped. Download the project to get the full contents of the code. We will only be discussing what is relevant to the observer pattern. We will not discuss the other parts of the Java EE Kickoff App.
AnEvent instance of typeUser is created by the container and is injected into the subject class. When the fire method is invoked, a notification is fired and any observer listening forUser events will receive the user instance. Any method parameter annotated with@Observes and of typeUser will receive this instance of the user object. This is the observer pattern in its simplest form.
Create the observer classes.
LoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassLoginService { publicvoidsaveLoginAttempt(@ObservesUser user) { System.out.println("Logged in: "+ user.getEmail()); }} |
EmailLoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassEmailLoginService { publicvoidsendLoginAttemptEmail(@ObservesUser user) { System.out.println(user.getEmail() +" has been used to log into Java EE Observer Design Pattern Example."); }} |
The above methods with parameters annotated with@Observes will receive a notification when a user logs in. When we log in, we should have the statements above printed.
Try logging as anadmin and then as auser. We should see the output below.
Console Output
1 2 3 4 5 6 | 11:18:31,908 INFO [org.omnifaces.eventlistener.FacesRequestLogger] (default task-1) GET={url=/login, user={ip=127.0.0.1, login=null, session=zMpJHJSEodlYr0tY8-giDAciJIDcMdEStJT_6lcQ, viewState=null}, action={source=null, event=null, methods=[], validationFailed=false}, params={}, messages={}, timer={0=220ms, 1=3ms, 2=-1ms, 3=-1ms, 4=-1ms, 5=-1ms, 6=217ms}}11:18:49,943 INFO [stdout] (default task-1) Loggedin: admin@kickoff.example.org11:18:49,943 INFO [stdout] (default task-1) admin@kickoff.example.org has been used to log into Java EE Observer Design Pattern Example.11:18:49,946 INFO [org.omnifaces.eventlistener.FacesRequestLogger] (default task-1) POST={url=/login, user={ip=127.0.0.1, login=admin@kickoff.example.org, session=pnrQwJj3ao-mJoPd3RmEc_I-ompITHeimrs8XvDw, viewState=stateless}, action={source=loginForm:login, |
Those were the basics related to the J2EE observer pattern.
5. @Priority
The order in which observers are invoked is not specified. We can use thePriority annotation to specify the invocation order of the observers. This is a new feature in Java EE 8. The lowest value priority is called first. If the priorities are the same then they are invoked in an uncertain order.
LoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassLoginService { publicvoidsaveLoginAttempt(@Observes@Priority(10) User user) { System.out.println("Logged in: "+ user.getEmail()); }} |
EmailLoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassEmailLoginService { publicvoidsendLoginAttemptEmail(@Observes@Priority(100) User user) { System.out.println(user.getEmail() +" has been used to log into Java EE Observer Design Pattern Example."); }} |
With the above priority configuration, the “Logging in…” is printed first followed by “Java EE Observer Design Pattern…”. Try swapping the number so that “Java EE Observer Design Pattern…” is printed first.
6. @Qualifier
So far our observers are listening forUser type events. What if we want to distinguish between different types of users? Theadmin and theuser type? At the moment both methods get invoked when a user logs in. What if we only want to save a login attempt if it’s of typeuser? What if we only want to send an email if the login attempt is of typeadmin? In order for the observers to distinguish between events, we will use aQualifier.
UserEvent.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | packageorg.example.kickoff.business.service;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;importjavax.inject.Qualifier;@Qualifier@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.PARAMETER})public@interfaceUserEvent { Type value(); enumType {ADMIN, USER}} |
We will then use theUserEvent annotation to distinguish which observer to invoke like so.
LoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassLoginService { publicvoidsaveLoginAttempt(@Observes@Priority(10)@UserEvent(UserEvent.Type.USER) User user) { System.out.println("Logged in: "+ user.getEmail()); }} |
EmailLoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.Observes;importorg.example.kickoff.model.User;publicclassEmailLoginService { publicvoidsendLoginAttemptEmail(@Observes@Priority(100)@UserEvent(UserEvent.Type.ADMIN) User user) { System.out.println(user.getEmail() +" has been used to log into Java EE Observer Design Pattern Example."); }} |
And then editAuthBacking, adding a new event like so.
AuthBacking.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | packageorg.example.kickoff.view.auth;...snipped...importorg.example.kickoff.business.service.UserEvent;...snipped...publicabstractclassAuthBacking { ...snipped... @Inject@UserEvent(UserEvent.Type.USER) privateEvent<User> userLoginEvent; @Inject@UserEvent(UserEvent.Type.ADMIN) privateEvent<User> adminLoginEvent; ...snipped... protectedvoidauthenticate(AuthenticationParameters parameters)throwsIOException { AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters); if(status == SEND_FAILURE) { addGlobalError("auth.message.error.failure"); validationFailed(); } elseif(status == SEND_CONTINUE) { responseComplete();// Prevent JSF from rendering a response so authentication mechanism can continue. } elseif(activeUser.hasGroup(ADMIN)) { adminLoginEvent.fire(user); redirect("admin/users"); } elseif(activeUser.hasGroup(USER)) { userLoginEvent.fire(user); redirect("user/profile"); } else{ redirect(""); } } ...snipped...} |
With the above code, whenever an admin logs in, an email is sent. Whenever a user logs in, it is saved. Our observers can now distinguish which subject has changed its states.

Thank you!
We will contact you soon.
7. Asynchronous Observer
By default events are synchronous. In CDI 2.0, a new firing method calledfireAsync and a corresponding observer annotationObservesAsync handles asynchronous processing of events. We cannot set a priority because events are observed asynchronously in separate threads. Asynchronous and synchronous observers operate independently from each other. This means the synchronous firing of events are not observed by asynchronous observers and vice versa. We’ll need to changeLoginService, EmailLoginService, andAuthBacking like so.
LoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.ObservesAsync;importorg.example.kickoff.model.User;publicclassLoginService { publicvoidsaveLoginAttempt(@ObservesAsync@UserEvent(UserEvent.Type.USER) User user) { System.out.println("Logged in: "+ user.getEmail()); }} |
EmailLoginService.java
01 02 03 04 05 06 07 08 09 10 | packageorg.example.kickoff.business.service;importjavax.enterprise.event.ObservesAsync;importorg.example.kickoff.model.User;publicclassEmailLoginService { publicvoidsendLoginAttemptEmail(@ObservesAsync@UserEvent(UserEvent.Type.ADMIN) User user) { System.out.println(user.getEmail() +" has been used to log into Java EE Observer Design Pattern Example."); }} |
AuthBacking.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | packageorg.example.kickoff.view.auth;...snipped...importjava.util.concurrent.CompletionStage;importorg.example.kickoff.business.service.UserEvent;...snipped...publicabstractclassAuthBacking { ...snipped... @Inject@UserEvent(UserEvent.Type.USER) privateEvent<User> userLoginEvent; @Inject@UserEvent(UserEvent.Type.ADMIN) privateEvent<User> adminLoginEvent; ...snipped... protectedvoidauthenticate(AuthenticationParameters parameters)throwsIOException { AuthenticationStatus status = securityContext.authenticate(getRequest(), getResponse(), parameters); if(status == SEND_FAILURE) { addGlobalError("auth.message.error.failure"); validationFailed(); } elseif(status == SEND_CONTINUE) { responseComplete();// Prevent JSF from rendering a response so authentication mechanism can continue. } elseif(activeUser.hasGroup(ADMIN)) { CompletionStage stage = adminLoginEvent.fireAsync(user); stage.handle((User event, Throwable e) -> { for(Throwable t : e.getSuppressed()) { System.out.println(t.getMessage()); } returnevent; }); redirect("admin/users"); } elseif(activeUser.hasGroup(USER)) { userLoginEvent.fireAsync(user, NotificationOptions.ofExecutor(newForkJoinPool(10))); redirect("user/profile"); } else{ redirect(""); } } ...snipped...} |
We have changed the methodfire tofireAsync. We have added notification options to our firing event and specified a thread pool. OurForkJoinPool allows 10 threads. This means if there are 10 or fewer observers, they will get executed asynchronously. If there are more, the other observers must wait until a thread becomes available.
ThefireAsync the method returns an instance ofCompletionStage. This instance holds a reference to all the exceptions thrown during observer invocation and can be handled in the same way that you would handle a completion state instance.
8. Java EE Observer Design Pattern Summary
That’s all there is to it. We started with a simple implementation of the observer pattern then moved on to use many of the Java EE features. Reaching to the advanced topic of asynchronous observers. Let us now look at the pros and cons of the J2EE Observer Design pattern.
8.1 Pros and cons of the Observer Design Pattern
In Java, the Observer pattern was introduced in Java 1.0. J2EE Observer design helps in development by using annotations and conventions over configuration. Resources also can be injected by type and using annotations@Inject and@Producer. This design pattern helps in startup behavioral characteristics. The developer has control over concurrency and access timeout. This design results in lesser boilerplate code. Any java object can be injected easily. Loosely coupled design and dependency injection can be achieved easily. Business logic is decoupled from the observer through events.
The disadvantage of this pattern is performance issues related to lazy loading. Eager loading creates memory issues. The overuse of this design pattern results in problems. An observer design pattern is suggested to be used in special cases. Type safety is an issue with named annotations. The creation of objects is not transparent. The execution flow is not visible to the developer due to events triggered by other events.
9. Download the Source Code
This is an example about Java EE Observer Design Pattern.
You can download the source code of this example here:Java EE Observer Design Pattern Example
Last updated on Jun. 22nd, 2020

Thank you!
We will contact you soon.







