Liferay 7 Notifications

So in a recent project I've been building I reached a point where I believed my project would benefit from being able to issue user notifications.

For those that are not aware, Liferay has a built-in system for subscribing and notifications.  Using these APIs, you can quickly add notifications to your projects.


Before diving into the implementation, let's talk about the foundation of the subscription and notification APIs.

The first thing you need to identify is your events.  Events are what users will subscribe to, and when events occur you want to issue notifications.  Knowing your list of events going in will make things easier as you'll know when you'll need to send notifications.

For this blog post we're going to be implementing an example, so I'm going to build a system to issue a notification when a user with the Administrator role logs in.  We all know that for effective site security no one should be using Administrator credentials because of the unlimited access Administrators have, so getting a notification when an Administrator logs in can be a good security test.

So we know the event, "Administrator has logged in", the rest of the blog will build out support for subscriptions and notifications.

One thing we will need to pull all of this together is a portlet module (since we have some interface requirements).  Let's start by building a new portlet module.  You can use your IDE, but to be IDE-neutral I'm going to stick with blade commands:

blade create -t mvcportlet -p com.dnebinger.admin.notification admin-notification

This will give us a new Liferay MVC portlet using our desired package and a simple project directory.

Subscribing To The Event

The first thing that users need to be able to do is to subscribe to your events.  Some portal examples are blogs (where a user can subscribe to all blogs or an individual blog for changes).  So the challenge for you is to identify where a user would subscribe to your event.  In some cases you would display right on the page (i.e. blogs and forum threads), in other cases you might want to move it to a configuration panel.

For this portlet, there is no real UI work, just going to have a JSP with a checkbox for subscribe/unsubscribe, but the important part is the Liferay subscription APIs we're going to use.

Subscription is handled by the com.liferay.portal.kernel.service.SubscriptionLocalService service.  When you're subscribing, you'll be using the addSubscription() method, and when you're unsubscribing you're going to use the deleteSubscription() method.

Arguments for the calls are:

  • userId: The user who is subscribing/unsubscribing.
  • groupId: (for adds) the group the user is subscribing to (for 'containers' like a doc lib folder or forum category).
  • className: The name of the class user wants to be monitored of changes on.
  • pkId: The primary key for the object to (un)subscribe to.

So normally you'll be building subscription into your SB entities, so it is common practice to put subscribe and unsubscribe methods into the service interface to combine the subscription features with the data access.

For this project, we don't really have an entity or a data access layer, so we're just going to handle subscribe/unsubscribe directly in our action command handler.  Also we don't really have a PK id so we'll just stick with an id of 0 and the portlet class as the class name.

  immediate = true,
  property = {
    "" + AdminNotificationPortletKeys.ADMIN_NOTIFICATION_PORTLET_KEY,
  service = MVCActionCommand.class
public class SubscribeMVCActionCommand extends BaseMVCActionCommand {
  protected void doProcessAction(ActionRequest actionRequest, ActionResponse actionResponse) throws Exception {
    String cmd = ParamUtil.getString(actionRequest, Constants.CMD);

    if (Validator.isNull(cmd)) {
      // an error

    long userId = PortalUtil.getUserId(actionRequest);

    if (Constants.SUBSCRIBE.equals(cmd)) {
      _subscriptionLocalService.addSubscription(userId, 0, AdminNotificationPortlet.class.getName(), 0);
    } else if (Constants.UNSUBSCRIBE.equals(cmd)) {
      _subscriptionLocalService.deleteSubscription(userId, AdminNotificationPortlet.class.getName(), 0);

  @Reference(unbind = "-")
  protected void setSubscriptionLocalService(final SubscriptionLocalService subscriptionLocalService) {
    _subscriptionLocalService = subscriptionLocalService;

  private SubscriptionLocalService _subscriptionLocalService;

User Notification Preferences

Users can manage their notification preferences separately from portlets which issue notifications.  When you go to the "My Account" area of the side bar, there's a "Notifications" option.  When you click this link, you will normally see your list of notifications.  But if you click on the dot menu in the upper right corner, you can choose "Configuration" to see all of the magic.

This page has a collapsable area for each registered notifying portlet and within each area is a line item for a type of notification and sliders to recieve notifications by email and/or website (assuming the portlet has indicated that it supports both types of notifications).  In the future Liferay or your team might add more notification methods (i.e. SMS or other), and for those cases the portlet just needs to indicate that it also supports the notification type.

So how do we get our collapsable panel registered to appear on this page?  Well, through the magic of OSGi DS service annotations, of course!

There are two types of classes that we need to implement.  The first extends the com.liferay.portal.kernel.notifications.UserNotificationDefinition class.  As the class name suggests, this class provides the definition of the type of notifications the portlet can send and the notification types it supports.

  immediate = true,
  property = {"" + AdminNotificationPortletKeys.ADMIN_NOTIFICATION},
  service = UserNotificationDefinition.class
public class AdminLoginUserNotificationDefinition extends UserNotificationDefinition {
  public AdminLoginUserNotificationDefinition() {
    // pass in our portlet key, 0 for a class name id (don't care about it), the notification type (not really), and
    // finally the resource bundle key for the message the user sees.
    super(AdminNotificationPortletKeys.ADMIN_NOTIFICATION, 0,

    // add a notification type for each sort of notification that we want to support.
      new UserNotificationDeliveryType(
        "email", UserNotificationDeliveryConstants.TYPE_EMAIL, true, true));
      new UserNotificationDeliveryType(
        "website", UserNotificationDeliveryConstants.TYPE_WEBSITE, true, true));

This definition registers and informs notifications that we have a notification definition.  The constructor binds the notification to our custom portlet and provides the message key for the notification panel.  We also add two user notification delivery types that we'll support, one for sending an email and one for using the notification portlet.

When you go to the Notifications panel under My Account and choose the Configuration option from the menu in the dropdown on the upper-right corner, you can see the notification preferences:

Notification Type Selection

Handling Notifications

Another aspect of notifications is the UserNotificationHandler implementation.  The UserNotificationHandler's job is to interpret the notification event and determine whether to deliver the notification and build the UserNotificationFeedEntry (basically the notification message itself).

Liferay provides a number of base implementation classes that you can use to build your own UserNotificationHandler instance from:

  • com.liferay.portal.kernel.notifications.BaseUserNotificationHandler - This implements a simple user notification handler with points to override the body of the notification and some other key points, but for the most part it is capable of building all of the basic notification details.
  • com.liferay.portal.kernel.notifications.BaseModelUserNotificationHandler - This class is another base class suitable for asset-enabled entities for notifications.  It uses the AssetRenderer for the entity class to render the asset and this is used as the message for the notifcation.

Obviously if you have an asset-enabled entity you're notifying on, you'd want to use the BaseModelUserNotificationHandler.  For our implementation we're going to use BaseUserNotificationHandler as the base class:

  immediate = true,
  property = {"" + AdminNotificationPortletKeys.ADMIN_NOTIFICATION},
  service = UserNotificationHandler.class
public class AdminLoginUserNotificationHandler extends BaseUserNotificationHandler {

   * AdminLoginUserNotificationHandler: Constructor class.
  public AdminLoginUserNotificationHandler() {

  protected String getBody(UserNotificationEvent userNotificationEvent, ServiceContext serviceContext) throws Exception {
    String username = LanguageUtil.get(serviceContext.getLocale(), _UKNOWN_USER_KEY);
    // okay, we need to get the user for the event
    User user = _userLocalService.fetchUser(userNotificationEvent.getUserId());

    if (Validator.isNotNull(user)) {
      // get the company the user belongs to.
      Company company = _companyLocalService.fetchCompany(user.getCompanyId());

      // based on the company auth type, find the user name to display.
      // so we'll get screen name or email address or whatever they're using to log in.

      if (Validator.isNotNull(company)) {
        if (company.getAuthType().equals(CompanyConstants.AUTH_TYPE_EA)) {
          username = user.getEmailAddress();
        } else if (company.getAuthType().equals(CompanyConstants.AUTH_TYPE_SN)) {
          username = user.getScreenName();
        } else if (company.getAuthType().equals(CompanyConstants.AUTH_TYPE_ID)) {
          username = String.valueOf(user.getUserId());

    // we'll be stashing the client address in the payload of the event, so let's extract it here.
    JSONObject jsonObject = JSONFactoryUtil.createJSONObject(

    String fromHost = jsonObject.getString(Constants.FROM_HOST);

    // fetch our strings via the language bundle.
    String title = LanguageUtil.get(serviceContext.getLocale(), _TITLE_KEY);

    String body = LanguageUtil.format(serviceContext.getLocale(), _BODY_KEY, new Object[] {username, fromHost});

    // build the html using our template.
    String html = StringUtil.replace(_BODY_TEMPLATE, _BODY_REPLACEMENTS, new String[] {title, body});

    return html;

  @Reference(unbind = "-")
  protected void setUserLocalService(final UserLocalService userLocalService) {
    _userLocalService = userLocalService;
  @Reference(unbind = "-")
  protected void setCompanyLocalService(final CompanyLocalService companyLocalService) {
    _companyLocalService = companyLocalService;

  private UserLocalService _userLocalService;
  private CompanyLocalService _companyLocalService;

  private static final String _TITLE_KEY = "title.admin.login";
  private static final String _BODY_KEY = "body.admin.login";
  private static final String _UKNOWN_USER_KEY = "unknown.user";

  private static final String _BODY_TEMPLATE = "<div class=\"title\">[$TITLE$]</div><div class=\"body\">[$BODY$]</div>";
  private static final String[] _BODY_REPLACEMENTS = new String[] {"[$TITLE$]", "[$BODY$]"};

  private static final Log _log = LogFactoryUtil.getLog(AdminLoginUserNotificationHandler.class);

This handler basically builds the body of the notification itself which will be based on the admin login details (user and where they are logging in from).  When building out your own notification, you'll likely want to be using the notification event payload to pass details.  We're going to pass just the host where the admin is coming from so our payload is as simple as it gets, but you could easily pass XML or JSON or whatever structured string you want to pass necessary notification details.

This handler just makes the same notification body for both email and notification portlet display, no difference between the two.  Since the method is passed the UserNotificationEvent, you can use the getDeliveryType() method to build different bodies depending upon whether you are building an email notification or a notification portlet display message.

Publishing Notification Events

So far we have code to allow users to subscribe to our administrator login event, we allow them to choose how they want to receive the notifications, and we also have code to transform the notification event into a notification message, what remains is actually issuing the notification events themselves.

This is very much going to be dependent upon your event source.  Most Liferay events are based on the addition or modification of some entity, so it is common to find their event publishing code in the service implementation classes when the entities are added or updated.  Your own notification events can come from wherever the event originates, even outside of the service layer.

Our notification event is based on an administrator login; the best way to publish these kinds of events is through a post login component.  We'll define the new component as such:

  immediate = true, property = {""},
  service = LifecycleAction.class
public class AdminLoginNotificationEventSender implements LifecycleAction {

  public void processLifecycleEvent(LifecycleEvent lifecycleEvent)
      throws ActionException {

    // get the request associated with the event
    HttpServletRequest request = lifecycleEvent.getRequest();

    // get the user associated with the event
    User user = null;

    try {
      user = PortalUtil.getUser(request);
    } catch (PortalException e) {
      // failed to get the user, just ignore this

    if (user == null) {
      // failed to get a valid user, just return.

    // We have the user, but are they an admin?
    PermissionChecker permissionChecker = null;

    try {
      permissionChecker = PermissionCheckerFactoryUtil.create(user);
    } catch (Exception e) {
      // ignore the exception

    if (permissionChecker == null) {
      // failed to get a permission checker

    // If the permission checker indicates the user is not omniadmin, nothing to report.
    if (! permissionChecker.isOmniadmin()) {

    // this user is an administrator, need to issue the event
    ServiceContext serviceContext = null;

    try {
      // create a service context for the call
      serviceContext = ServiceContextFactory.getInstance(request);

      // note that when you're behind an LB, the remote host may be the address
      // for the LB instead of the remote client.  In these cases the LB will often
      // add a request header with a special key that holds the remote client host
      // so you'd want to use that if it is available.
      String fromHost = request.getRemoteHost();

      // notify subscribers
      notifySubscribers(user.getUserId(), fromHost, user.getCompanyId(), serviceContext);
    } catch (PortalException e) {
      // ignored

  protected void notifySubscribers(long userId, String fromHost, long companyId, ServiceContext serviceContext)
      throws PortalException {

    // so all of this stuff should normally come from some kind of configuration.
    // As this is just an example, we're using a lot of hard coded values and values.
    String entryTitle = "Admin User Login";

    String fromName = PropsUtil.get(Constants.EMAIL_FROM_NAME);
    String fromAddress = GetterUtil.getString(PropsUtil.get(Constants.EMAIL_FROM_ADDRESS), PropsUtil.get(PropsKeys.ADMIN_EMAIL_FROM_ADDRESS));

    LocalizedValuesMap subjectLocalizedValuesMap = new LocalizedValuesMap();
    LocalizedValuesMap bodyLocalizedValuesMap = new LocalizedValuesMap();

    subjectLocalizedValuesMap.put(Locale.ENGLISH, "Administrator Login");
    bodyLocalizedValuesMap.put(Locale.ENGLISH, "Adminstrator has logged in.");

    AdminLoginSubscriptionSender subscriptionSender =
        new AdminLoginSubscriptionSender();



    subscriptionSender.setFrom(fromAddress, fromName);

    int notificationType = AdminNotificationType.NOTIFICATION_TYPE_ADMINISTRATOR_LOGIN;


    String portletId = PortletProviderUtil.getPortletId(AdminNotificationPortletKeys.ADMIN_NOTIFICATION, PortletProvider.Action.VIEW);



        AdminNotificationPortlet.class.getName(), 0);


This is a LifecycleAction component that registers as a post login lifecycle event listener.  It goes through a series of checks to determine if the user is an administrator and, when they are, it issues a notification.  The real fun happens in the notifySubscribers() method.

This method has a lot of initialization and setting of the subscriptionSender properties.  This variable is of type AdminLoginSubscriptionSender, a class which extends SubscriptionSender.  This is the guy that handles the actual notification sending.

The flushNotificationAsync() method pushes the instance onto the Liferay Message Bus where a message receiver gets the SubscriptionSender and invokes its flushNotification() method (you can call this method too if you don't need async notification sending).

The flushNotification() method does some permission checking, user verification, filtering (i.e. don't send a notification to the user that generated the event) and eventually sends the email notification and/or adds the user notification for the notifications portlet.

The AdminLoginSubscriptionSender class is pretty simple:

public class AdminLoginSubscriptionSender extends SubscriptionSender {

  private static final long serialVersionUID = -7152698157653361441L;

  protected void populateNotificationEventJSONObject(
      JSONObject notificationEventJSONObject) {


    notificationEventJSONObject.put(Constants.FROM_HOST, _fromHost);

  protected boolean hasPermission(Subscription subscription, String className, long classPK, User user) throws Exception {
    return true;

  protected boolean hasPermission(Subscription subscription, User user) throws Exception {
    return true;

  protected void sendNotification(User user) throws Exception {
    // remove the super classes filtering of not notifying user who is self.
    // makes sense in most cases, but we want a notification of admin login so
    // we know when never any admin logs in from anywhere at any time.

    // will be a pain if we get notified because of our own login, but we want to
    // know if some hacker gets our admin credentials and logs in and it's not really us.


  public void setFromHost(String fromHost) {
    this._fromHost = fromHost;

  private String _fromHost;

Putting It All Together

Okay, so now we have everything:

  • We have code to allow users to subscribe to the admin login events.
  • We have code to allow users to select how they receive notifications.
  • We have code to transform the notification message in the database into HTML for display in the notifications portlet.
  • We have code to send the notifications when an administrator logs in.

Now we can test it all out.  After building and deploying the module, you're pretty much ready to go.

You'll have to log in and sign up to receive the notifications.  Note if you're using your local test Liferay environment you might not have email enabled so be sure to use the web notifications.  In fact, in my DXP environment I don't have email configured and I got a slew of exceptions from the Liferay email subsystem; I ignored them since they are from not having email set up.

Then log out and log in again as an administrator and you should see your notification pop up in the left sidebar.

Admin Notifications Messages


So there you have it, basic code that will support creating and sending notifications.  You can use this code to add notifications support into your own portlets and take them whereever you need.

You can find the code for this project up on github:


Thanks David, this is fantastic stuff.

For anyone else who might make the same mistake that I did -
Don't forget to add the Admin Login Notification portlet to a page (on your dashboard for example) and click on the Subscribe button.

It seems to me that this example is not integrated with the 7.0 user notification management, based on UserNotificationDeliveryService. If a user goes to his  Notifications/Configuration panel and subscribes email or website notifications, these subscriptions are ignored. That's why you need to have the portlet and subscribe through it, storing the subscription on the SubscriptionService.  

Even i am looking for the similar functionality, but
I want to notify all site users when a content is created and added on page for a particular site by a Content Management team.
please help.
Thanks & regards,
Mohit Mehral
Hi Mohit,

Did you find any solution for this??
If yes then please share as I am having the same requirement.

Waheed Khan

Hii Mohit,  Did you find any solution for this?? If yes then please share as I am having the same requirement. Thanks & regards, Ronak Patel

There are two parts to sending a notification: 1) sending the notification, that's covered by this blog and 2) identifying when to send a notification.  That's not covered by this blog, but it is something that you need to figure out per your own requirement.

I will say that doing a notification when content is added to a page is actually pretty hard, mostly because there are legitimate ways to add content that appear on a page but are not placed there.

An asset publisher is a typical example; if you add an AP, you configure it for the content to display, sometimes you cast a wide net and get a list, but you can also be rather narrow and come up with a handful or even a single asset. Carousels built around AP are like this. But the important part is that content can be created, will appear on a page, but won't be directly placed there by an admin.

Display pages on content can be another example.  A final example is content versioning; you can create a new version of content, but Liferay will typically display the most recent version. So changing a content can change the page w/o direct intervention.

This is not like an old html site where developers have to directly place content on pages and therefore, on page change, you can issue notifications. Liferay is designed from the ground up to be a dynamic system, and changes can be added/updated/removed without touching a page.

Hi David, It's really nice blog.
do you have any list of out of the box portlet where liferay dxp provides notifications?

Great article.  There is one issue with it when using LR 7 fp 57.  The handler is only used for website/push notifications.  It's never used for email notifications to render the body.  I sure wish this was not the case.  Perhaps this was the case at time of writing.  Anyways, this was a great sample that got me going with Notifications.  Thanks!

Nice blog post, but there is something in the code that is bugging me...


AdminLoginNotificationEventSender.notifySubscribers: The values put into the Maps subjectLocalizedValuesMap and bodyLocalizedValuesMap are never used.


Good catch, Dave, I must have missed that. The SubscriptionSender has two methods, setLocalizedSubjectMap() and setLocalizedBodyMap() methods that would be invoked to pass in the subject and body maps.

Thanks for adding that info, David!


I'm currently in the woes of writing my own UserNotification type; and I want to share some pitfalls and tidbits here:


At one point my notifications did turn up, but I couldn't mark them as read, as unread, nor could I delete them. They just show up without the ellipsis menu and all buttons in the top bar are disabled when I select such a notification. In this case, something went wrong with the UserNotificationDefinition: probably the default constructor hasn't been overridden.


I even managed to send a notification that showed up as "Notification isn't valid anymore" right from the start (I did use the wrong portletId String, so there's no Handler class registered for that portletId)... but I cannot delete that one in the UI. It's just there and cannot be deleted. I can only say that one better doesn't make that test on a production server ;-)


You can actually skip the whole Subscription stuff and send a single Notification (of TYPE_WEBSITE) to a single user using UserNotificationEventLocalService.sendUserNotificationEvents(), which accepts a JSON object as payload. You'll still need to implement the Handler and Definition classes, but this allows you to send arbitrary plain text notifications to single users.

Also, why does this comment system double my empty lines???

You should never test anything in production ;-)



Notifications are tricky kind of like the asset framework can be; all of your ducks have to line up in a row. If you miss a piece, it can break notification functionality and it can be hard to figure out why.



You're getting double newlines, but mine were stripped entirely. It's a known issue, but trying to balance the editor, theme, antispam and other capabilities along with the cross-browser requirements, I'm just uncertain if it is an easy problem for them to solve or not.

You mention "all of your ducks have to line up in a row" ... yeah, that hits the heart of my gripes with liferay's documentation. I've never found any docs listing the minimal set of ducks needed to achieve a given goal (in other words: what is the implicit contract that Liferay is assuming that my code fulfills). 

Something like: "To show a custom notification, you need the following things in place: a) a Definition class, linked to an arbitrary portlet name (using the @component property, which provides a service for UserNotificationDefinition.class (using the @component service argument), extending UserNotificationDefinition, having a default constructor which calls super(String, long, int, String) and one or more of the add*DeliveryType methods to register properly. b) a Handler class, linked to the same portlet name as the Definition class, providing a service of UserNotificationHandler.class, extending BaseUserNotificationHandler, calling setPortletId in its default constructor, overriding setBody(), where you must not return null nor StringPool.BLANK, but get the payload from the given UserNotificationEvent and interpret it and return that (the payload is always a JSON, because the notification sending methods want a JSONObject; also, you are responsible to come up with a schema for this JSON and adhere to it whenever you send a notification). c) A sender method, which builds said payload as a JSONObject and ultimately calls userNotificationEventLocalService.sendUserNotificationEvents (whatever you pass as notificationEventJSONObject here comes out in the Handler class as a payload String; yes, the naming is inconsistent here) or derives from SubscriptionSender (see contract docs for Notification event subscription). -- Furthermore, the following options exist: 1) when sending, you can set the options "delivered" and "actionRequired", which have the following consequences: [I never found out the actual list of consequences] 2) in your handler class, you can override the getters of some flags to change them: getActionable, getApplicable, getOpenDialog, getSelector. [I never found out what exactly they are supposed to do]."


Without such documentation, coding for Liferay just feels like mining tunnel after tunnel into hard rock, trying assumption after assumption on how the underlying contract might look like, but only at the end of each tunnel you know whether this tunnel has merit. And when you find your goal at the end of the tenth tunnel or so, you mark the right tunnel, but you're still not sure if you did oversee something.


Regarding the editor: In this concrete case here, I think it could be solved if <p> elements get the same top/bottom margins in editor and in view and/or removing empty <p> after paragraph detection. But, ultimately, I think that problem stems from the detection of "what is a newline and what is a paragraph?", with some users insisting on hitting enter at the end of each line, even if they want to create a flowing paragraph, as if they're still working on a mechanical typewriter.

Anyway, I've seen this problem pop up and down on Liferay (in Forum and Blogs components) as long as I've been working with it... i.e. since 6.1.0rc1. Currently here, it's the combination of all errors I've seen yet: single newlines are removed and double newlines produce an empty paragraph. It's as if with each ckeditor update, someone randomly remembers or forgets to apply that one patch that streamlines this behaviour.

Hi David,


I'm using Liferay  7 CE server and have many custom portlets installed in it. I have one custom entity/table named partner which has LicenceUpto Date. On every login event and site page opening event we have to validate License expiration, if expired we have to show notification that 'Your License expired'. can you please give idea how to achieve that?  

There's a couple of ways to skin this cat...



Build this out as a portlet. One that does the check. It would render nothing if license is fine or add this banner (not a notification) if it is expired. Then drop it on every page and let it sit there.




Another way would be to embed it into portal_normal in your theme to invoke a local method on the service, i.e. isLicenceExpired() (one you write yourself) and then use in the FM as an indicator to add the banner.




Notifications, as presented here, are really more of "event notifications", i.e. you have a new workflow task or someone commented on your blog, those kinds of things. The thing you are describing to me isn't really an event notification as much as it is more of a system status.

Hi David,


Thanks for the reply! Just want to add something, I want to show notification/alert as like below message : 'Danger Due to inactivity, your session has expired. Please save any data you may have entered before refreshing the page.' This notification/alert comes when user session has time out.

One more thing, I'm using and events in which I have to validate License expiration and from that I have to send notification/alerts like session expiration.  


Hi, Hello, very good publication, thank you. It has served as a guide because I am implementing a module in which the user can create personalized notifications through a form that consists of two inputs for the title and body of the notification and finally a submit button. When you click on the submit button the notification is created, however I have modified the code, specifically the AdminLoginUserNotificationHandler class to take this information from the view but all the notifications are taking the same text, even the ones that were previously created. Why is this happening?