You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/05/19 16:39:50 UTC

[fineract] branch develop updated: Deleting the topics module since its incomplete and additional cleanups

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 6849f45bb Deleting the topics module since its incomplete and additional cleanups
6849f45bb is described below

commit 6849f45bb84cef9ea207fd8092c429f685a33d7c
Author: Arnold Galovics <ga...@gmail.com>
AuthorDate: Tue May 17 22:10:30 2022 +0200

    Deleting the topics module since its incomplete and additional cleanups
---
 .../groovy/org.apache.fineract.dependencies.gradle |   1 +
 .../notification/api/NotificationApiResource.java  |  33 +--
 .../api/NotificationApiResourceSwagger.java        |  68 ++++++
 .../notification/data/NotificationData.java        | 104 +--------
 .../fineract/notification/data/TopicData.java      |  74 ------
 .../notification/data/TopicSubscriberData.java     |  61 -----
 .../fineract/notification/domain/Notification.java |   8 +-
 .../notification/domain/NotificationMapper.java    |   8 +-
 .../apache/fineract/notification/domain/Topic.java | 121 ----------
 .../notification/domain/TopicRepository.java       |  30 ---
 .../notification/domain/TopicSubscriber.java       |  77 -------
 .../domain/TopicSubscriberRepository.java          |  29 ---
 ...java => ActiveMQNotificationEventListener.java} |  37 ++-
 ...ava => ActiveMQNotificationEventPublisher.java} |  31 +--
 .../NotificationEventListener.java                 |  65 ++----
 .../NotificationEventPublisher.java}               |   9 +-
 .../eventandlistener/SpringEventListener.java      |  84 -------
 ...r.java => SpringNotificationEventListener.java} |  26 ++-
 ....java => SpringNotificationEventPublisher.java} |  14 +-
 .../exception/TopicNotFoundException.java          |  37 ---
 .../service/NotificationDomainServiceImpl.java     |  61 ++---
 ...ificationGeneratorWritePlatformServiceImpl.java |   8 +-
 .../NotificationReadPlatformServiceImpl.java       |  25 +--
 .../service/NotificationWritePlatformService.java  |   4 +-
 .../NotificationWritePlatformServiceImpl.java      |  40 +---
 .../notification/service/TopicDomainService.java   |  44 ----
 .../service/TopicDomainServiceImpl.java            | 248 ---------------------
 .../service/TopicReadPlatformService.java          |  32 ---
 .../service/TopicReadPlatformServiceImpl.java      |  96 --------
 .../TopicSubscriberReadPlatformService.java        |  28 ---
 .../TopicSubscriberReadPlatformServiceImpl.java    |  77 -------
 ...riberWritePlatformServiceJpaRepositoryImpl.java |  42 ----
 .../service/TopicWritePlatformService.java         |  27 ---
 ...TopicWritePlatformServiceJpaRepositoryImpl.java |  42 ----
 ...fficeWritePlatformServiceJpaRepositoryImpl.java |  32 +--
 .../api/UsersApiResourceSwagger.java               |   7 +-
 .../useradministration/domain/AppUser.java         |   7 +-
 .../domain/AppUserRepository.java                  |   3 +
 ...pUserWritePlatformServiceJpaRepositoryImpl.java |  43 +---
 .../RoleWritePlatformServiceJpaRepositoryImpl.java |  33 +--
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../changelog/tenant/parts/0013_remove_topics.xml  |  27 +++
 integration-tests/build.gradle                     |   4 +-
 integration-tests/dependencies.gradle              |   1 +
 .../integrationtests/NotificationApiTest.java      |  79 +++++--
 .../common/NotificationHelper.java                 |  42 ++--
 .../integrationtests/common/OfficeHelper.java      |  11 +
 .../fineract/integrationtests/common/Utils.java    |   9 +-
 .../useradministration/roles/RolesHelper.java      |   2 +
 .../useradministration/users/UserHelper.java       |  18 +-
 50 files changed, 419 insertions(+), 1591 deletions(-)

diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
index cc01b28f8..0e82e6b66 100644
--- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
+++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
@@ -166,6 +166,7 @@ dependencyManagement {
         }
 
         dependency 'org.owasp.esapi:esapi:2.4.0.0'
+        dependency 'org.awaitility:awaitility:4.2.0'
 
         dependencySet(group: 'org.apache.poi', version: '5.2.2') {
             entry 'poi'
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java
index 5ae26807c..88f8cf94f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java
@@ -18,6 +18,12 @@
  */
 package org.apache.fineract.notification.api;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -28,6 +34,7 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
 import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
@@ -36,15 +43,12 @@ import org.apache.fineract.infrastructure.core.service.SearchParameters;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.notification.data.NotificationData;
 import org.apache.fineract.notification.service.NotificationReadPlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 
 @Path("/notifications")
 @Component
-@Scope("singleton")
-
 @Tag(name = "Notification", description = "")
+@RequiredArgsConstructor
 public class NotificationApiResource {
 
     private final PlatformSecurityContext context;
@@ -52,21 +56,18 @@ public class NotificationApiResource {
     private final ApiRequestParameterHelper apiRequestParameterHelper;
     private final ToApiJsonSerializer<NotificationData> toApiJsonSerializer;
 
-    @Autowired
-    public NotificationApiResource(PlatformSecurityContext context, NotificationReadPlatformService notificationReadPlatformService,
-            ApiRequestParameterHelper apiRequestParameterHelper, ToApiJsonSerializer<NotificationData> toApiJsonSerializer) {
-        this.context = context;
-        this.notificationReadPlatformService = notificationReadPlatformService;
-        this.apiRequestParameterHelper = apiRequestParameterHelper;
-        this.toApiJsonSerializer = toApiJsonSerializer;
-    }
-
     @GET
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
-    public String getAllNotifications(@Context final UriInfo uriInfo, @QueryParam("orderBy") final String orderBy,
-            @QueryParam("limit") final Integer limit, @QueryParam("offset") final Integer offset,
-            @QueryParam("sortOrder") final String sortOrder, @QueryParam("isRead") final boolean isRead) {
+    @Operation
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = NotificationApiResourceSwagger.GetNotificationsResponse.class))) })
+    public String getAllNotifications(@Context final UriInfo uriInfo,
+            @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
+            @QueryParam("limit") @Parameter(description = "limit") final Integer limit,
+            @QueryParam("offset") @Parameter(description = "offset") final Integer offset,
+            @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
+            @QueryParam("isRead") @Parameter(description = "isRead") final boolean isRead) {
 
         this.context.authenticatedUser();
         final Page<NotificationData> notificationData;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResourceSwagger.java
new file mode 100644
index 000000000..1036e0257
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResourceSwagger.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.notification.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+
+final class NotificationApiResourceSwagger {
+
+    private NotificationApiResourceSwagger() {}
+
+    @Schema(description = "GetNotificationsResponse")
+    public static final class GetNotificationsResponse {
+
+        private GetNotificationsResponse() {}
+
+        static final class GetNotification {
+
+            private GetNotification() {}
+
+            @Schema(example = "1")
+            public Long id;
+            @Schema(example = "a")
+            public String objectType;
+            @Schema(example = "1")
+            public Long objectId;
+            @Schema(example = "a")
+            public String action;
+            @Schema(example = "1")
+            public Long actorId;
+            @Schema(example = "a")
+            public String content;
+            @Schema(example = "true")
+            public boolean isRead;
+            @Schema(example = "true")
+            public boolean isSystemGenerated;
+            @Schema(example = "a")
+            public String tenantIdentifier;
+            @Schema(example = "a")
+            public String createdAt;
+            @Schema(example = "1")
+            public Long officeId;
+            @Schema(example = "[]")
+            public List<Long> userIds;
+        }
+
+        @Schema(example = "10")
+        public int totalFilteredRecords;
+
+        public List<GetNotification> pageItems;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/data/NotificationData.java b/fineract-provider/src/main/java/org/apache/fineract/notification/data/NotificationData.java
index 978970220..f67cc81a7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/data/NotificationData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/data/NotificationData.java
@@ -19,8 +19,10 @@
 package org.apache.fineract.notification.data;
 
 import java.io.Serializable;
-import java.util.List;
+import java.util.Set;
+import lombok.Data;
 
+@Data
 public class NotificationData implements Serializable {
 
     private Long id;
@@ -34,12 +36,12 @@ public class NotificationData implements Serializable {
     private String tenantIdentifier;
     private String createdAt;
     private Long officeId;
-    private List<Long> userIds;
+    private Set<Long> userIds;
 
     public NotificationData() {}
 
     public NotificationData(String objectType, Long objectId, String action, Long actorId, String content, boolean isSystemGenerated,
-            boolean isRead, String tenantIdentifier, Long officeId, List<Long> userIds) {
+            boolean isRead, String tenantIdentifier, Long officeId, Set<Long> userIds) {
         this.objectType = objectType;
         this.objectId = objectId;
         this.action = action;
@@ -51,100 +53,4 @@ public class NotificationData implements Serializable {
         this.officeId = officeId;
         this.userIds = userIds;
     }
-
-    public Long getOfficeId() {
-        return officeId;
-    }
-
-    public void setOfficeId(Long officeId) {
-        this.officeId = officeId;
-    }
-
-    public List<Long> getUserIds() {
-        return userIds;
-    }
-
-    public void setUserId(List<Long> userIds) {
-        this.userIds = userIds;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public String getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(String createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public String getObjectType() {
-        return objectType;
-    }
-
-    public void setObjectType(String objectType) {
-        this.objectType = objectType;
-    }
-
-    public Long getObjectIdentfier() {
-        return objectId;
-    }
-
-    public void entifier(Long objectIdentifier) {
-        this.objectId = objectIdentifier;
-    }
-
-    public String getAction() {
-        return action;
-    }
-
-    public void setAction(String action) {
-        this.action = action;
-    }
-
-    public Long getActor() {
-        return actorId;
-    }
-
-    public void setActor(Long actorId) {
-        this.actorId = actorId;
-    }
-
-    public String getContent() {
-        return content;
-    }
-
-    public void setContent(String content) {
-        this.content = content;
-    }
-
-    public boolean isRead() {
-        return this.isRead;
-    }
-
-    public void setRead(boolean isRead) {
-        this.isRead = isRead;
-    }
-
-    public boolean isSystemGenerated() {
-        return isSystemGenerated;
-    }
-
-    public void setSystemGenerated(boolean systemGenerated) {
-        isSystemGenerated = systemGenerated;
-    }
-
-    public String getTenantIdentifier() {
-        return tenantIdentifier;
-    }
-
-    public void setTenantIdentifier(String tenantIdentifier) {
-        this.tenantIdentifier = tenantIdentifier;
-    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicData.java b/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicData.java
deleted file mode 100644
index 9ab2ced60..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicData.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.data;
-
-import java.io.Serializable;
-
-public class TopicData implements Serializable {
-
-    private final Long id;
-    private final String title;
-    private final boolean enabled;
-    private final Long entityId;
-    private final String entityType;
-    private final String memberType;
-
-    public TopicData(Long id, String title, boolean enabled, Long entityId, String entityType, String memberType) {
-        this.id = id;
-        this.title = title;
-        this.enabled = enabled;
-        this.entityId = entityId;
-        this.entityType = entityType;
-        this.memberType = memberType;
-    }
-
-    public TopicData(Long id, String title, Long entityId, String entityType, String memberType) {
-        this.id = id;
-        this.title = title;
-        this.enabled = true;
-        this.entityId = entityId;
-        this.entityType = entityType;
-        this.memberType = memberType;
-    }
-
-    public Long getId() {
-        return this.id;
-    }
-
-    public String getTitle() {
-        return this.title;
-    }
-
-    public boolean isEnabled() {
-        return this.enabled;
-    }
-
-    public Long getEntityId() {
-        return this.entityId;
-    }
-
-    public String getEntityType() {
-        return this.entityType;
-    }
-
-    public String getMemberType() {
-        return this.memberType;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicSubscriberData.java b/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicSubscriberData.java
deleted file mode 100644
index 5607a21a0..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/data/TopicSubscriberData.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.data;
-
-import java.time.LocalDate;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
-
-public class TopicSubscriberData {
-
-    private final Long id;
-    private final Long topicId;
-    private final Long userId;
-    private final LocalDate subscriptionDate;
-
-    public TopicSubscriberData(Long id, Long topicId, Long userId, LocalDate subscriptionDate) {
-        this.id = id;
-        this.topicId = topicId;
-        this.userId = userId;
-        this.subscriptionDate = subscriptionDate;
-    }
-
-    public TopicSubscriberData(Long id, Long topicId, Long userId) {
-        this.id = id;
-        this.topicId = topicId;
-        this.userId = userId;
-        this.subscriptionDate = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
-    }
-
-    public Long getId() {
-        return this.id;
-    }
-
-    public Long getTopicId() {
-        return this.topicId;
-    }
-
-    public Long getUserId() {
-        return this.userId;
-    }
-
-    public LocalDate getSubscriptionDate() {
-        return this.subscriptionDate;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Notification.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Notification.java
index fcc403c7d..66e3c052f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Notification.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Notification.java
@@ -18,9 +18,12 @@
  */
 package org.apache.fineract.notification.domain;
 
+import java.util.Date;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 
 @Entity
@@ -46,12 +49,13 @@ public class Notification extends AbstractPersistableCustom {
     private String notificationContent;
 
     @Column(name = "created_at")
-    private String createdAt;
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createdAt;
 
     public Notification() {}
 
     public Notification(String objectType, Long objectIdentifier, String action, Long actorId, boolean isSystemGenerated,
-            String notificationContent, String createdAt) {
+            String notificationContent, Date createdAt) {
         this.objectType = objectType;
         this.objectIdentifier = objectIdentifier;
         this.action = action;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/NotificationMapper.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/NotificationMapper.java
index 670a58bdb..a75b8d1a8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/NotificationMapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/NotificationMapper.java
@@ -18,11 +18,14 @@
  */
 package org.apache.fineract.notification.domain;
 
+import java.util.Date;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 import org.apache.fineract.useradministration.domain.AppUser;
 
@@ -42,11 +45,12 @@ public class NotificationMapper extends AbstractPersistableCustom {
     private boolean isRead;
 
     @Column(name = "created_at")
-    private String createdAt;
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createdAt;
 
     public NotificationMapper() {}
 
-    public NotificationMapper(Notification notification, AppUser userId, boolean isRead, String createdAt) {
+    public NotificationMapper(Notification notification, AppUser userId, boolean isRead, Date createdAt) {
         this.notification = notification;
         this.userId = userId;
         this.isRead = isRead;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Topic.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Topic.java
deleted file mode 100644
index 80a530069..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/Topic.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.domain;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Table;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
-
-@Entity
-@Table(name = "topic")
-public class Topic extends AbstractPersistableCustom {
-
-    @Column(name = "title", unique = true, nullable = false, length = 100)
-    private String title;
-
-    @Column(name = "enabled", nullable = false)
-    private Boolean enabled;
-
-    @Column(name = "entity_id", nullable = false)
-    private Long entityId;
-
-    @Column(name = "entity_type")
-    private String entityType;
-
-    @Column(name = "member_type")
-    private String memberType;
-
-    public Topic() {}
-
-    public Topic(String title, Boolean enabled, Long entityId, String entityType, String memberType) {
-        this.title = title.trim();
-        this.enabled = enabled;
-        this.entityId = entityId;
-        this.entityType = entityType.trim();
-        this.memberType = memberType.trim();
-    }
-
-    public static Topic fromJson(final JsonCommand command) {
-        String title = "";
-        Boolean enabled = null;
-        Long entityId = 0L;
-        String entityType = "";
-        String memberType = "";
-
-        if (command.hasParameter("title")) {
-            title = command.stringValueOfParameterNamed("title");
-        }
-        if (command.hasParameter("enabled")) {
-            enabled = command.booleanPrimitiveValueOfParameterNamed("enabled");
-        }
-        if (command.hasParameter("entityId")) {
-            entityId = command.longValueOfParameterNamed("entityId");
-        }
-        if (command.hasParameter("entityType")) {
-            entityType = command.stringValueOfParameterNamed("entityType");
-        }
-        if (command.hasParameter("memberType")) {
-            memberType = command.stringValueOfParameterNamed("memberType");
-        }
-        return new Topic(title, enabled, entityId, entityType, memberType);
-    }
-
-    public String getTitle() {
-        return this.title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public Boolean getEnabled() {
-        return this.enabled;
-    }
-
-    public void setEnabled(Boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public Long getEntityId() {
-        return this.entityId;
-    }
-
-    public void setEntityId(Long entityId) {
-        this.entityId = entityId;
-    }
-
-    public String getEntityType() {
-        return this.entityType;
-    }
-
-    public void setEntityType(String entityType) {
-        this.entityType = entityType;
-    }
-
-    public String getMemberType() {
-        return this.memberType;
-    }
-
-    public void setMemberType(String memberType) {
-        this.memberType = memberType;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicRepository.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicRepository.java
deleted file mode 100644
index c3b3edb76..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicRepository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.domain;
-
-import java.util.List;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-
-public interface TopicRepository extends JpaRepository<Topic, Long>, JpaSpecificationExecutor<Topic> {
-
-    List<Topic> findByEntityId(Long entityId);
-
-    List<Topic> findByMemberType(String memberType);
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriber.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriber.java
deleted file mode 100644
index 918d4a047..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriber.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.domain;
-
-import java.util.Date;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.Table;
-import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
-import org.apache.fineract.useradministration.domain.AppUser;
-
-@Entity
-@Table(name = "topic_subscriber")
-public class TopicSubscriber extends AbstractPersistableCustom {
-
-    @ManyToOne
-    @JoinColumn(name = "topic_id")
-    private Topic topic;
-
-    @ManyToOne
-    @JoinColumn(name = "user_id")
-    private AppUser subscriber;
-
-    @Column(name = "subscription_date")
-    private Date subscriptionDate;
-
-    public TopicSubscriber() {}
-
-    public TopicSubscriber(Topic topic, AppUser subscriber, Date subscriptionDate) {
-        this.topic = topic;
-        this.subscriber = subscriber;
-        this.subscriptionDate = subscriptionDate;
-    }
-
-    public Topic getTopic() {
-        return this.topic;
-    }
-
-    public void setTopic(Topic topic) {
-        this.topic = topic;
-    }
-
-    public AppUser getSubscriber() {
-        return this.subscriber;
-    }
-
-    public void setSubscriber(AppUser subscriber) {
-        this.subscriber = subscriber;
-    }
-
-    public Date getSubscriptionDate() {
-        return this.subscriptionDate;
-    }
-
-    public void setSubscriptionDate(Date subscriptionDate) {
-        this.subscriptionDate = subscriptionDate;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriberRepository.java b/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriberRepository.java
deleted file mode 100644
index 09a75de6c..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/domain/TopicSubscriberRepository.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.domain;
-
-import java.util.List;
-import org.apache.fineract.useradministration.domain.AppUser;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-
-public interface TopicSubscriberRepository extends JpaRepository<TopicSubscriber, Long>, JpaSpecificationExecutor<TopicSubscriber> {
-
-    List<TopicSubscriber> findBySubscriber(AppUser subscriber);
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventListener.java
similarity index 55%
copy from fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java
copy to fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventListener.java
index 801754121..1798082f4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventListener.java
@@ -18,33 +18,28 @@
  */
 package org.apache.fineract.notification.eventandlistener;
 
-import javax.jms.Destination;
 import javax.jms.JMSException;
 import javax.jms.Message;
+import javax.jms.ObjectMessage;
 import javax.jms.Session;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.notification.data.NotificationData;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jms.core.JmsTemplate;
-import org.springframework.jms.core.MessageCreator;
-import org.springframework.stereotype.Service;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jms.listener.SessionAwareMessageListener;
+import org.springframework.stereotype.Component;
 
-@Service
-public class NotificationEventService {
+@Component
+@Profile("activeMqEnabled")
+@RequiredArgsConstructor
+public class ActiveMQNotificationEventListener implements SessionAwareMessageListener {
 
-    private final JmsTemplate jmsTemplate;
+    private final NotificationEventListener notificationEventListener;
 
-    @Autowired
-    public NotificationEventService(JmsTemplate jmsTemplate) {
-        this.jmsTemplate = jmsTemplate;
-    }
-
-    public void broadcastNotification(final Destination destination, final NotificationData notificationData) {
-        this.jmsTemplate.send(destination, new MessageCreator() {
-
-            @Override
-            public Message createMessage(Session session) throws JMSException {
-                return session.createObjectMessage(notificationData);
-            }
-        });
+    @Override
+    public void onMessage(Message message, Session session) throws JMSException {
+        if (message instanceof ObjectMessage) {
+            NotificationData notificationData = (NotificationData) ((ObjectMessage) message).getObject();
+            notificationEventListener.receive(notificationData);
+        }
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventPublisher.java
similarity index 58%
rename from fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java
rename to fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventPublisher.java
index 801754121..66830f49b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/ActiveMQNotificationEventPublisher.java
@@ -18,33 +18,24 @@
  */
 package org.apache.fineract.notification.eventandlistener;
 
-import javax.jms.Destination;
-import javax.jms.JMSException;
-import javax.jms.Message;
-import javax.jms.Session;
+import javax.jms.Queue;
+import lombok.RequiredArgsConstructor;
+import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.fineract.notification.data.NotificationData;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
 import org.springframework.jms.core.JmsTemplate;
-import org.springframework.jms.core.MessageCreator;
 import org.springframework.stereotype.Service;
 
 @Service
-public class NotificationEventService {
+@Profile("activeMqEnabled")
+@RequiredArgsConstructor
+public class ActiveMQNotificationEventPublisher implements NotificationEventPublisher {
 
     private final JmsTemplate jmsTemplate;
 
-    @Autowired
-    public NotificationEventService(JmsTemplate jmsTemplate) {
-        this.jmsTemplate = jmsTemplate;
-    }
-
-    public void broadcastNotification(final Destination destination, final NotificationData notificationData) {
-        this.jmsTemplate.send(destination, new MessageCreator() {
-
-            @Override
-            public Message createMessage(Session session) throws JMSException {
-                return session.createObjectMessage(notificationData);
-            }
-        });
+    @Override
+    public void broadcastNotification(NotificationData notificationData) {
+        Queue queue = new ActiveMQQueue("NotificationQueue");
+        this.jmsTemplate.send(queue, session -> session.createObjectMessage(notificationData));
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java
index 84fbc5fca..f6946eea1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java
@@ -21,10 +21,8 @@ package org.apache.fineract.notification.eventandlistener;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import javax.jms.JMSException;
-import javax.jms.Message;
-import javax.jms.ObjectMessage;
-import javax.jms.Session;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
@@ -32,57 +30,42 @@ import org.apache.fineract.notification.data.NotificationData;
 import org.apache.fineract.notification.service.NotificationWritePlatformService;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.fineract.useradministration.domain.AppUserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jms.listener.SessionAwareMessageListener;
 import org.springframework.stereotype.Service;
 
 @Service
-public class NotificationEventListener implements SessionAwareMessageListener {
+@RequiredArgsConstructor
+public class NotificationEventListener {
 
     private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
-
     private final NotificationWritePlatformService notificationWritePlatformService;
-
     private final AppUserRepository appUserRepository;
 
-    @Autowired
-    public NotificationEventListener(BasicAuthTenantDetailsService basicAuthTenantDetailsService,
-            NotificationWritePlatformService notificationWritePlatformService, AppUserRepository appUserRepository) {
-        this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
-        this.notificationWritePlatformService = notificationWritePlatformService;
-        this.appUserRepository = appUserRepository;
-    }
-
-    @Override
-    public void onMessage(Message message, Session session) throws JMSException {
-        if (message instanceof ObjectMessage) {
-            NotificationData notificationData = (NotificationData) ((ObjectMessage) message).getObject();
-
-            final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(notificationData.getTenantIdentifier(),
-                    false);
-            ThreadLocalContextUtil.setTenant(tenant);
+    public void receive(NotificationData notificationData) {
+        final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(notificationData.getTenantIdentifier(),
+                false);
+        ThreadLocalContextUtil.setTenant(tenant);
 
-            Long appUserId = notificationData.getActor();
+        Long appUserId = notificationData.getActorId();
 
-            List<Long> userIds = notificationData.getUserIds();
+        Set<Long> userIds = notificationData.getUserIds();
 
-            if (notificationData.getOfficeId() != null) {
-                List<Long> tempUserIds = new ArrayList<>(userIds);
-                for (Long userId : tempUserIds) {
-                    AppUser appUser = appUserRepository.findById(userId).get();
-                    if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) {
-                        userIds.remove(userId);
-                    }
+        if (notificationData.getOfficeId() != null) {
+            List<Long> tempUserIds = new ArrayList<>(userIds);
+            for (Long userId : tempUserIds) {
+                AppUser appUser = appUserRepository.findById(userId).get();
+                if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) {
+                    userIds.remove(userId);
                 }
             }
+        }
 
-            if (userIds.contains(appUserId)) {
-                userIds.remove(appUserId);
-            }
-
-            notificationWritePlatformService.notify(userIds, notificationData.getObjectType(), notificationData.getObjectIdentfier(),
-                    notificationData.getAction(), notificationData.getActor(), notificationData.getContent(),
-                    notificationData.isSystemGenerated());
+        // Don't notify the same user who triggered the event
+        if (userIds.contains(appUserId)) {
+            userIds.remove(appUserId);
         }
+
+        notificationWritePlatformService.notify(userIds, notificationData.getObjectType(), notificationData.getObjectId(),
+                notificationData.getAction(), notificationData.getActorId(), notificationData.getContent(),
+                notificationData.isSystemGenerated());
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventPublisher.java
similarity index 77%
rename from fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformService.java
rename to fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventPublisher.java
index 4cca2cd46..7a2af40cb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventPublisher.java
@@ -16,12 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.notification.service;
+package org.apache.fineract.notification.eventandlistener;
 
-import org.apache.fineract.notification.domain.TopicSubscriber;
+import org.apache.fineract.notification.data.NotificationData;
 
-public interface TopicSubscriberWritePlatformService {
-
-    Long create(TopicSubscriber topicSubscriber);
+public interface NotificationEventPublisher {
 
+    void broadcastNotification(NotificationData notificationData);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventListener.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventListener.java
deleted file mode 100644
index 942ebe82b..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventListener.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.eventandlistener;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
-import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
-import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
-import org.apache.fineract.notification.data.NotificationData;
-import org.apache.fineract.notification.service.NotificationWritePlatformService;
-import org.apache.fineract.useradministration.domain.AppUser;
-import org.apache.fineract.useradministration.domain.AppUserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationListener;
-import org.springframework.stereotype.Component;
-
-@Component
-public class SpringEventListener implements ApplicationListener<SpringEvent> {
-
-    private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
-
-    private final NotificationWritePlatformService notificationWritePlatformService;
-
-    private final AppUserRepository appUserRepository;
-
-    @Autowired
-    public SpringEventListener(BasicAuthTenantDetailsService basicAuthTenantDetailsService,
-            NotificationWritePlatformService notificationWritePlatformService, AppUserRepository appUserRepository) {
-        this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
-        this.notificationWritePlatformService = notificationWritePlatformService;
-        this.appUserRepository = appUserRepository;
-    }
-
-    @Override
-    public void onApplicationEvent(SpringEvent event) {
-        NotificationData notificationData = event.getNotificationData();
-
-        final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(notificationData.getTenantIdentifier(),
-                false);
-        ThreadLocalContextUtil.setTenant(tenant);
-
-        Long appUserId = notificationData.getActor();
-
-        List<Long> userIds = notificationData.getUserIds();
-
-        if (notificationData.getOfficeId() != null) {
-            List<Long> tempUserIds = new ArrayList<>(userIds);
-            for (Long userId : tempUserIds) {
-                AppUser appUser = appUserRepository.findById(userId).get();
-                if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) {
-                    userIds.remove(userId);
-                }
-            }
-        }
-
-        if (userIds.contains(appUserId)) {
-            userIds.remove(appUserId);
-        }
-
-        notificationWritePlatformService.notify(userIds, notificationData.getObjectType(), notificationData.getObjectIdentfier(),
-                notificationData.getAction(), notificationData.getActor(), notificationData.getContent(),
-                notificationData.isSystemGenerated());
-
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
similarity index 55%
copy from fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java
copy to fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
index 3de0f1758..e57be9a06 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
@@ -18,19 +18,25 @@
  */
 package org.apache.fineract.notification.eventandlistener;
 
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.notification.data.NotificationData;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.stereotype.Service;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
 
-@Service
-public class SpringEventPublisher {
+@Component
+@Profile("!activeMqEnabled")
+@RequiredArgsConstructor
+@Slf4j
+public class SpringNotificationEventListener implements ApplicationListener<SpringEvent> {
 
-    @Autowired
-    private ApplicationEventPublisher applicationEventPublisher;
+    private final NotificationEventListener notificationEventListener;
 
-    public void broadcastNotification(final NotificationData notificationData) {
-        SpringEvent event = new SpringEvent(this, notificationData);
-        applicationEventPublisher.publishEvent(event);
+    @Override
+    public void onApplicationEvent(SpringEvent event) {
+        log.debug("Processing Spring notification event {}", event);
+        NotificationData notificationData = event.getNotificationData();
+        notificationEventListener.receive(notificationData);
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventPublisher.java
similarity index 74%
rename from fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java
rename to fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventPublisher.java
index 3de0f1758..15547e3cf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringEventPublisher.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventPublisher.java
@@ -18,18 +18,24 @@
  */
 package org.apache.fineract.notification.eventandlistener;
 
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.notification.data.NotificationData;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Service;
 
 @Service
-public class SpringEventPublisher {
+@Profile("!activeMqEnabled")
+@RequiredArgsConstructor
+@Slf4j
+public class SpringNotificationEventPublisher implements NotificationEventPublisher {
 
-    @Autowired
-    private ApplicationEventPublisher applicationEventPublisher;
+    private final ApplicationEventPublisher applicationEventPublisher;
 
+    @Override
     public void broadcastNotification(final NotificationData notificationData) {
+        log.debug("Sending Spring notification event: {}", notificationData);
         SpringEvent event = new SpringEvent(this, notificationData);
         applicationEventPublisher.publishEvent(event);
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/exception/TopicNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/notification/exception/TopicNotFoundException.java
deleted file mode 100644
index 4d02d0327..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/exception/TopicNotFoundException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.exception;
-
-import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
-import org.springframework.dao.EmptyResultDataAccessException;
-
-/**
- * A {@link RuntimeException} thrown when topic resources are not found.
- */
-public class TopicNotFoundException extends AbstractPlatformResourceNotFoundException {
-
-    public TopicNotFoundException(final Long id) {
-        super("error.msg.topic.id.invalid", "Topic with identifier " + id + " does not exist", id);
-    }
-
-    public TopicNotFoundException(Long id, EmptyResultDataAccessException e) {
-        super("error.msg.topic.id.invalid", "Topic with identifier " + id + " does not exist", id, e);
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
index 793d51c7c..1e703bdd7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
@@ -18,23 +18,19 @@
  */
 package org.apache.fineract.notification.service;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.PostConstruct;
-import javax.jms.Queue;
 import lombok.RequiredArgsConstructor;
-import org.apache.activemq.command.ActiveMQQueue;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.notification.data.NotificationData;
-import org.apache.fineract.notification.data.TopicSubscriberData;
-import org.apache.fineract.notification.eventandlistener.NotificationEventService;
-import org.apache.fineract.notification.eventandlistener.SpringEventPublisher;
-import org.apache.fineract.organisation.office.domain.OfficeRepository;
+import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
 import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity;
@@ -49,21 +45,19 @@ import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
 import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
-import org.apache.fineract.useradministration.domain.Role;
-import org.apache.fineract.useradministration.domain.RoleRepository;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserRepository;
 import org.springframework.stereotype.Service;
 
 @Service
 @RequiredArgsConstructor
+@Slf4j
 public class NotificationDomainServiceImpl implements NotificationDomainService {
 
     private final BusinessEventNotifierService businessEventNotifierService;
     private final PlatformSecurityContext context;
-    private final RoleRepository roleRepository;
-    private final OfficeRepository officeRepository;
-    private final TopicSubscriberReadPlatformService topicSubscriberReadPlatformService;
-    private final NotificationEventService notificationEvent;
-    private final SpringEventPublisher springEventPublisher;
+    private final NotificationEventPublisher notificationEventPublisher;
+    private final AppUserRepository appUserRepository;
 
     @PostConstruct
     public void addListeners() {
@@ -387,39 +381,20 @@ public class NotificationDomainServiceImpl implements NotificationDomainService
             String eventType, Long appUserId, Long officeId) {
 
         String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
-        Queue queue = new ActiveMQQueue("NotificationQueue");
-        List<Long> userIds = retrieveSubscribers(officeId, permission);
+        Set<Long> userIds = getNotifiableUserIds(officeId, permission);
         NotificationData notificationData = new NotificationData(objectType, objectIdentifier, eventType, appUserId, notificationContent,
                 false, false, tenantIdentifier, officeId, userIds);
         try {
-            this.notificationEvent.broadcastNotification(queue, notificationData);
+            notificationEventPublisher.broadcastNotification(notificationData);
         } catch (Exception e) {
-            this.springEventPublisher.broadcastNotification(notificationData);
+            // We want to avoid rethrowing the exception to stop the business transaction from rolling back
+            log.error("Error while broadcasting notification event", e);
         }
     }
 
-    private List<Long> retrieveSubscribers(Long officeId, String permission) {
-
-        Set<TopicSubscriberData> topicSubscribers = new HashSet<>();
-        List<Long> subscriberIds = new ArrayList<>();
-        Long entityId = officeId;
-        String entityType = "";
-        if (officeRepository.findById(entityId).get().getParent() == null) {
-            entityType = "OFFICE";
-        } else {
-            entityType = "BRANCH";
-        }
-        List<Role> allRoles = roleRepository.findAll();
-        for (Role curRole : allRoles) {
-            if (curRole.hasPermissionTo(permission) || curRole.hasPermissionTo("ALL_FUNCTIONS")) {
-                String memberType = curRole.getName();
-                topicSubscribers.addAll(topicSubscriberReadPlatformService.getSubscribers(entityId, entityType, memberType));
-            }
-        }
-
-        for (TopicSubscriberData topicSubscriber : topicSubscribers) {
-            subscriberIds.add(topicSubscriber.getUserId());
-        }
-        return subscriberIds;
+    private Set<Long> getNotifiableUserIds(Long officeId, String permission) {
+        Collection<AppUser> users = appUserRepository.findByOfficeId(officeId);
+        Collection<AppUser> usersWithPermission = users.stream().filter(aU -> aU.hasAnyPermission(permission, "ALL_FUNCTIONS")).toList();
+        return usersWithPermission.stream().map(AppUser::getId).collect(toSet());
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorWritePlatformServiceImpl.java
index 3cad192e0..8824e6be3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationGeneratorWritePlatformServiceImpl.java
@@ -18,21 +18,17 @@
  */
 package org.apache.fineract.notification.service;
 
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.notification.domain.Notification;
 import org.apache.fineract.notification.domain.NotificationRepository;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
+@RequiredArgsConstructor
 public class NotificationGeneratorWritePlatformServiceImpl implements NotificationGeneratorWritePlatformService {
 
     private final NotificationRepository notificationRepository;
 
-    @Autowired
-    public NotificationGeneratorWritePlatformServiceImpl(NotificationRepository notificationRepository) {
-        this.notificationRepository = notificationRepository;
-    }
-
     @Override
     public Long create(Notification notification) {
         this.notificationRepository.saveAndFlush(notification);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java
index a780cb75d..ae19bad6a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java
@@ -22,6 +22,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.List;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.PaginationHelper;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
@@ -32,32 +33,24 @@ import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
 import org.apache.fineract.notification.cache.CacheNotificationResponseHeader;
 import org.apache.fineract.notification.data.NotificationData;
 import org.apache.fineract.notification.data.NotificationMapperData;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Service;
 
 @Service
+@RequiredArgsConstructor
 public class NotificationReadPlatformServiceImpl implements NotificationReadPlatformService {
 
+    private HashMap<Long, HashMap<Long, CacheNotificationResponseHeader>> tenantNotificationResponseHeaderCache = new HashMap<>();
+
+    private final NotificationDataRow notificationDataRow = new NotificationDataRow();
+    private final NotificationMapperRow notificationMapperRow = new NotificationMapperRow();
+
     private final JdbcTemplate jdbcTemplate;
     private final PlatformSecurityContext context;
     private final ColumnValidator columnValidator;
     private final PaginationHelper paginationHelper;
     private final DatabaseSpecificSQLGenerator sqlGenerator;
-    private final NotificationDataRow notificationDataRow = new NotificationDataRow();
-    private final NotificationMapperRow notificationMapperRow = new NotificationMapperRow();
-    private HashMap<Long, HashMap<Long, CacheNotificationResponseHeader>> tenantNotificationResponseHeaderCache = new HashMap<>();
-
-    @Autowired
-    public NotificationReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final PlatformSecurityContext context,
-            final ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
-        this.jdbcTemplate = jdbcTemplate;
-        this.context = context;
-        this.columnValidator = columnValidator;
-        this.paginationHelper = paginationHelper;
-        this.sqlGenerator = sqlGenerator;
-    }
 
     @Override
     public boolean hasUnreadNotifications(Long appUserId) {
@@ -204,10 +197,10 @@ public class NotificationReadPlatformServiceImpl implements NotificationReadPlat
             notificationData.setObjectType(objectType);
 
             final Long objectId = rs.getLong("objectId");
-            notificationData.entifier(objectId);
+            notificationData.setObjectId(objectId);
 
             final Long actorId = rs.getLong("actor");
-            notificationData.setActor(actorId);
+            notificationData.setActorId(actorId);
 
             final String action = rs.getString("action");
             notificationData.setAction(action);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java
index e2a51d441..d0999baf6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java
@@ -18,13 +18,13 @@
  */
 package org.apache.fineract.notification.service;
 
-import java.util.List;
+import java.util.Collection;
 
 public interface NotificationWritePlatformService {
 
     Long notify(Long userId, String objectType, Long objectId, String action, Long actorId, String notificationContent,
             boolean isSystemGenerated);
 
-    Long notify(List<Long> userIds, String objectType, Long objectId, String action, Long actorId, String notificationContent,
+    Long notify(Collection<Long> userIds, String objectType, Long objectId, String action, Long actorId, String notificationContent,
             boolean isSystemGenerated);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java
index 3318bdc5b..a70e8408f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java
@@ -18,39 +18,28 @@
  */
 package org.apache.fineract.notification.service;
 
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Date;
+import java.util.Collection;
 import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.notification.domain.Notification;
 import org.apache.fineract.notification.domain.NotificationMapper;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.fineract.useradministration.domain.AppUserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@Transactional
+@RequiredArgsConstructor
 public class NotificationWritePlatformServiceImpl implements NotificationWritePlatformService {
 
     private final NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService;
-
     private final NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper;
-
     private final AppUserRepository appUserRepository;
-
     private final NotificationMapperWritePlatformService notificationMapperWritePlatformService;
 
-    @Autowired
-    public NotificationWritePlatformServiceImpl(final NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService,
-            final NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper,
-            final AppUserRepository appUserRepository,
-            final NotificationMapperWritePlatformService notificationMapperWritePlatformService) {
-        this.notificationGeneratorWritePlatformService = notificationGeneratorWritePlatformService;
-        this.notificationGeneratorReadRepositoryWrapper = notificationGeneratorReadRepositoryWrapper;
-        this.appUserRepository = appUserRepository;
-        this.notificationMapperWritePlatformService = notificationMapperWritePlatformService;
-    }
-
     @Override
     public Long notify(Long userId, String objectType, Long objectIdentifier, String action, Long actorId, String notificationContent,
             boolean isSystemGenerated) {
@@ -64,7 +53,8 @@ public class NotificationWritePlatformServiceImpl implements NotificationWritePl
     private Long insertIntoNotificationMapper(Long userId, Long generatedNotificationId) {
         AppUser appUser = this.appUserRepository.findById(userId).orElse(null);
         NotificationMapper notificationMapper = new NotificationMapper(
-                this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId), appUser, false, getCurrentDateTime());
+                this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId), appUser, false,
+                DateUtils.getDateOfTenant());
 
         this.notificationMapperWritePlatformService.create(notificationMapper);
         return notificationMapper.getId();
@@ -74,13 +64,13 @@ public class NotificationWritePlatformServiceImpl implements NotificationWritePl
             String notificationContent, boolean isSystemGenerated) {
 
         Notification notification = new Notification(objectType, objectIdentifier, action, actorId, isSystemGenerated, notificationContent,
-                getCurrentDateTime());
+                DateUtils.getDateOfTenant());
 
         return this.notificationGeneratorWritePlatformService.create(notification);
     }
 
     @Override
-    public Long notify(List<Long> userIds, String objectType, Long objectId, String action, Long actorId, String notificationContent,
+    public Long notify(Collection<Long> userIds, String objectType, Long objectId, String action, Long actorId, String notificationContent,
             boolean isSystemGenerated) {
 
         Long generatedNotificationId = insertIntoNotificationGenerator(objectType, objectId, action, actorId, notificationContent,
@@ -90,22 +80,16 @@ public class NotificationWritePlatformServiceImpl implements NotificationWritePl
         return generatedNotificationId;
     }
 
-    private List<Long> insertIntoNotificationMapper(List<Long> userIds, Long generatedNotificationId) {
+    private List<Long> insertIntoNotificationMapper(Collection<Long> userIds, Long generatedNotificationId) {
         List<Long> mappedIds = new ArrayList<>();
         for (Long userId : userIds) {
             AppUser appUser = this.appUserRepository.findById(userId).get();
             NotificationMapper notificationMapper = new NotificationMapper(
                     this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId), appUser, false,
-                    getCurrentDateTime());
+                    DateUtils.getDateOfTenant());
             this.notificationMapperWritePlatformService.create(notificationMapper);
             mappedIds.add(notificationMapper.getId());
         }
         return mappedIds;
     }
-
-    private String getCurrentDateTime() {
-        Date date = new Date();
-        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        return formatter.format(date);
-    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainService.java
deleted file mode 100644
index 1379b8e34..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainService.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.util.Map;
-import org.apache.fineract.organisation.office.domain.Office;
-import org.apache.fineract.useradministration.domain.AppUser;
-import org.apache.fineract.useradministration.domain.Role;
-
-public interface TopicDomainService {
-
-    void createTopic(Office newOffice);
-
-    void createTopic(Role newRole);
-
-    void updateTopic(Office updatedOffice, Map<String, Object> changes);
-
-    void updateTopic(String previousRolename, Role updatedRole, Map<String, Object> changes);
-
-    void deleteTopic(Role role);
-
-    void subscribeUserToTopic(AppUser newUser);
-
-    void updateUserSubscription(AppUser userToUpdate, Map<String, Object> changes);
-
-    void unsubcribeUserFromTopic(AppUser user);
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainServiceImpl.java
deleted file mode 100644
index 7122bcfa6..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicDomainServiceImpl.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.fineract.notification.domain.Topic;
-import org.apache.fineract.notification.domain.TopicRepository;
-import org.apache.fineract.notification.domain.TopicSubscriber;
-import org.apache.fineract.notification.domain.TopicSubscriberRepository;
-import org.apache.fineract.organisation.office.domain.Office;
-import org.apache.fineract.organisation.office.domain.OfficeRepository;
-import org.apache.fineract.useradministration.domain.AppUser;
-import org.apache.fineract.useradministration.domain.Role;
-import org.apache.fineract.useradministration.domain.RoleRepository;
-import org.apache.fineract.useradministration.exception.RoleNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.ObjectUtils;
-
-@Service
-@Transactional
-public class TopicDomainServiceImpl implements TopicDomainService {
-
-    private final RoleRepository roleRepository;
-    private final TopicRepository topicRepository;
-    private final OfficeRepository officeRepository;
-    private final TopicSubscriberRepository topicSubscriberRepository;
-
-    @Autowired
-    public TopicDomainServiceImpl(RoleRepository roleRepository, TopicRepository topicRepository, OfficeRepository officeRepository,
-            TopicSubscriberRepository topicSubscriberRepository) {
-
-        this.roleRepository = roleRepository;
-        this.topicRepository = topicRepository;
-        this.officeRepository = officeRepository;
-        this.topicSubscriberRepository = topicSubscriberRepository;
-    }
-
-    @Override
-    public void createTopic(Office newOffice) {
-
-        Long entityId = newOffice.getId();
-        String entityType = this.getEntityType(newOffice);
-
-        List<Role> allRoles = roleRepository.findAll();
-        for (Role role : allRoles) {
-            String memberType = role.getName().toUpperCase();
-            String title = role.getName() + " of " + newOffice.getName();
-            Topic newTopic = new Topic(title, true, entityId, entityType, memberType);
-            topicRepository.save(newTopic);
-        }
-    }
-
-    @Override
-    public void createTopic(Role newRole) {
-
-        List<Office> offices = officeRepository.findAll();
-
-        for (Office office : offices) {
-            String entityType = this.getEntityType(office);
-            String title = newRole.getName() + " of " + office.getName();
-            Topic newTopic = new Topic(title, true, office.getId(), entityType, newRole.getName().toUpperCase());
-            topicRepository.save(newTopic);
-        }
-    }
-
-    @Override
-    public void updateTopic(Office updatedOffice, Map<String, Object> changes) {
-
-        List<Topic> entityTopics = topicRepository.findByEntityId(updatedOffice.getId());
-
-        if (changes.containsKey("parentId")) {
-
-            String entityType = this.getEntityType(updatedOffice);
-            for (Topic topic : entityTopics) {
-                topic.setEntityType(entityType);
-                topicRepository.save(topic);
-            }
-        }
-        if (changes.containsKey("name")) {
-
-            for (Topic topic : entityTopics) {
-                Role role = roleRepository.getRoleByName(topic.getMemberType());
-                String title = role.getName() + " of " + updatedOffice.getName();
-                topic.setTitle(title);
-                topicRepository.save(topic);
-            }
-        }
-    }
-
-    @Override
-    public void updateTopic(String previousRolename, Role updatedRole, Map<String, Object> changes) {
-
-        if (changes.containsKey("name")) {
-            List<Topic> entityTopics = topicRepository.findByMemberType(previousRolename);
-            for (Topic topic : entityTopics) {
-                Office office = officeRepository.findById(topic.getEntityId()).get();
-                String title = updatedRole.getName() + " of " + office.getName();
-                topic.setTitle(title);
-                topic.setMemberType(updatedRole.getName().toUpperCase());
-                topicRepository.save(topic);
-            }
-        }
-    }
-
-    @Override
-    public void deleteTopic(Role role) {
-
-        List<Topic> topics = topicRepository.findByMemberType(role.getName().toUpperCase());
-        for (Topic topic : topics) {
-            topicRepository.delete(topic);
-        }
-    }
-
-    @Override
-    public void subscribeUserToTopic(AppUser newUser) {
-
-        List<Topic> possibleTopics = topicRepository.findByEntityId(newUser.getOffice().getId());
-
-        if (!possibleTopics.isEmpty()) {
-            Set<Role> userRoles = newUser.getRoles();
-            for (Role role : userRoles) {
-                for (Topic topic : possibleTopics) {
-                    if (role.getName().compareToIgnoreCase(topic.getMemberType()) == 0) {
-                        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, newUser, new Date());
-                        topicSubscriberRepository.save(topicSubscriber);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void updateUserSubscription(AppUser userToUpdate, Map<String, Object> changes) {
-
-        List<TopicSubscriber> oldSubscriptions = topicSubscriberRepository.findBySubscriber(userToUpdate);
-        if (changes.containsKey("officeId")) {
-            final Long oldOfficeId = userToUpdate.getOffice().getId();
-            final Long newOfficeId = (Long) changes.get("officeId");
-            List<Topic> oldTopics = topicRepository.findByEntityId(oldOfficeId);
-            List<Topic> newTopics = topicRepository.findByEntityId(newOfficeId);
-
-            for (TopicSubscriber subscriber : oldSubscriptions) {
-                for (Topic topic : oldTopics) {
-                    if (subscriber.getTopic().getId().equals(topic.getId())) {
-                        topicSubscriberRepository.delete(subscriber);
-                    }
-                }
-            }
-
-            Set<Role> userRoles = userToUpdate.getRoles();
-            for (Role role : userRoles) {
-                for (Topic topic : newTopics) {
-                    if (role.getName().compareToIgnoreCase(topic.getMemberType()) == 0) {
-                        TopicSubscriber newSubscription = new TopicSubscriber(topic, userToUpdate, new Date());
-                        topicSubscriberRepository.save(newSubscription);
-                    }
-                }
-            }
-        }
-
-        if (changes.containsKey("roles")) {
-
-            final Set<Role> oldRoles = userToUpdate.getRoles();
-            final Set<Role> tempOldRoles = new HashSet<>(oldRoles);
-
-            final String[] roleIds = (String[]) changes.get("roles");
-            final Set<Role> updatedRoles = assembleSetOfRoles(roleIds);
-            final Set<Role> tempUpdatedRoles = new HashSet<>(updatedRoles);
-
-            tempOldRoles.removeAll(updatedRoles);
-            for (TopicSubscriber subscriber : oldSubscriptions) {
-                Topic topic = subscriber.getTopic();
-                for (Role role : tempOldRoles) {
-                    if (role.getName().compareToIgnoreCase(topic.getMemberType()) == 0) {
-                        topicSubscriberRepository.delete(subscriber);
-                    }
-                }
-            }
-
-            tempUpdatedRoles.removeAll(oldRoles);
-            List<Topic> newTopics = topicRepository.findByEntityId(userToUpdate.getOffice().getId());
-            for (Topic topic : newTopics) {
-                for (Role role : tempUpdatedRoles) {
-                    if (role.getName().compareToIgnoreCase(topic.getMemberType()) == 0) {
-                        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, userToUpdate, new Date());
-                        topicSubscriberRepository.save(topicSubscriber);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void unsubcribeUserFromTopic(AppUser user) {
-
-        List<TopicSubscriber> subscriptions = topicSubscriberRepository.findBySubscriber(user);
-        for (TopicSubscriber subscription : subscriptions) {
-            topicSubscriberRepository.delete(subscription);
-        }
-    }
-
-    private String getEntityType(Office office) {
-
-        if (office.getParent() == null) {
-            return "OFFICE";
-        } else {
-            return "BRANCH";
-        }
-    }
-
-    private Set<Role> assembleSetOfRoles(final String[] rolesArray) {
-
-        final Set<Role> allRoles = new HashSet<>();
-
-        if (!ObjectUtils.isEmpty(rolesArray)) {
-            for (final String roleId : rolesArray) {
-                final Long id = Long.valueOf(roleId);
-                final Role role = this.roleRepository.findById(id).orElseThrow(() -> new RoleNotFoundException(id));
-                allRoles.add(role);
-            }
-        }
-
-        return allRoles;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformService.java
deleted file mode 100644
index 616cb8036..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.util.Collection;
-import org.apache.fineract.notification.data.TopicData;
-
-public interface TopicReadPlatformService {
-
-    Collection<TopicData> getAllTopics();
-
-    Collection<TopicData> getAllEnabledTopics();
-
-    TopicData findById(Long id);
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformServiceImpl.java
deleted file mode 100644
index a27d8a666..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicReadPlatformServiceImpl.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Collection;
-import org.apache.fineract.notification.data.TopicData;
-import org.apache.fineract.notification.exception.TopicNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.dao.EmptyResultDataAccessException;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-import org.springframework.stereotype.Service;
-
-@Service
-public class TopicReadPlatformServiceImpl implements TopicReadPlatformService {
-
-    private final JdbcTemplate jdbcTemplate;
-
-    @Autowired
-    public TopicReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) {
-        this.jdbcTemplate = jdbcTemplate;
-    }
-
-    private static final class TopicMapper implements RowMapper<TopicData> {
-
-        private final String schema;
-
-        TopicMapper() {
-            final StringBuilder sqlBuilder = new StringBuilder(200);
-            sqlBuilder.append("t.id as id, t.title as title, t.enabled as enabled, ");
-            sqlBuilder.append("t.entity_id as entityId, t.entity_type as entityType, ");
-            sqlBuilder.append("t.member_type as memberType, from topic t");
-            this.schema = sqlBuilder.toString();
-        }
-
-        public String schema() {
-            return this.schema;
-        }
-
-        @Override
-        public TopicData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
-            final Long id = rs.getLong("id");
-            final String title = rs.getString("title");
-            final Boolean enabled = rs.getBoolean("enabled");
-            final Long entityId = rs.getLong("entityId");
-            final String entityType = rs.getString("entityType");
-            final String memberType = rs.getString("memberType");
-
-            return new TopicData(id, title, enabled, entityId, entityType, memberType);
-        }
-
-    }
-
-    @Override
-    public Collection<TopicData> getAllTopics() {
-        final TopicMapper tm = new TopicMapper();
-        String sql = "select " + tm.schema();
-        return this.jdbcTemplate.query(sql, tm); // NOSONAR
-    }
-
-    @Override
-    public Collection<TopicData> getAllEnabledTopics() {
-        final TopicMapper tm = new TopicMapper();
-        final String sql = "select " + tm.schema() + " where t.is_active = ?";
-        return this.jdbcTemplate.query(sql, tm, new Object[] { true }); // NOSONAR
-    }
-
-    @Override
-    public TopicData findById(Long topicId) {
-        try {
-            final TopicMapper tm = new TopicMapper();
-            final String sql = "select " + tm.schema() + " where t.id = ?";
-            return this.jdbcTemplate.queryForObject(sql, tm, new Object[] { topicId }); // NOSONAR
-        } catch (final EmptyResultDataAccessException e) {
-            throw new TopicNotFoundException(topicId, e);
-        }
-    }
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformService.java
deleted file mode 100644
index 1bfdfc699..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.util.Collection;
-import org.apache.fineract.notification.data.TopicSubscriberData;
-
-public interface TopicSubscriberReadPlatformService {
-
-    Collection<TopicSubscriberData> getSubscribers(Long entityId, String entityType, String memberType);
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformServiceImpl.java
deleted file mode 100644
index b34535e84..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberReadPlatformServiceImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.time.LocalDate;
-import java.util.Collection;
-import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
-import org.apache.fineract.notification.data.TopicSubscriberData;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-import org.springframework.stereotype.Service;
-
-@Service
-public class TopicSubscriberReadPlatformServiceImpl implements TopicSubscriberReadPlatformService {
-
-    private final JdbcTemplate jdbcTemplate;
-
-    @Autowired
-    public TopicSubscriberReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) {
-        this.jdbcTemplate = jdbcTemplate;
-    }
-
-    private static final class TopicSubscriberMapper implements RowMapper<TopicSubscriberData> {
-
-        private final String schema;
-
-        TopicSubscriberMapper() {
-            final StringBuilder sqlBuilder = new StringBuilder(200);
-            sqlBuilder.append("ts.id as id, ts.topic_id as topicId, ts.user_id as userId, ");
-            sqlBuilder.append("ts.subscription_date as subscriptionDate from topic_subscriber ts ");
-            sqlBuilder.append("WHERE ts.topic_id = ( SELECT id from topic WHERE entity_id = ? ");
-            sqlBuilder.append("AND entity_type = ? AND member_type = ? )");
-            this.schema = sqlBuilder.toString();
-        }
-
-        public String schema() {
-            return this.schema;
-        }
-
-        @Override
-        public TopicSubscriberData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
-            final Long id = rs.getLong("id");
-            final Long topicId = rs.getLong("topicId");
-            final Long userId = rs.getLong("userId");
-            final LocalDate subscriptionDate = JdbcSupport.getLocalDate(rs, "subscriptionDate");
-
-            return new TopicSubscriberData(id, topicId, userId, subscriptionDate);
-        }
-
-    }
-
-    @Override
-    public Collection<TopicSubscriberData> getSubscribers(Long entityId, String entityType, String memberType) {
-        final TopicSubscriberMapper tsm = new TopicSubscriberMapper();
-        final String sql = "SELECT " + tsm.schema();
-        return this.jdbcTemplate.query(sql, tsm, new Object[] { entityId, entityType, memberType }); // NOSONAR
-    }
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformServiceJpaRepositoryImpl.java
deleted file mode 100644
index 8d1bcf407..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicSubscriberWritePlatformServiceJpaRepositoryImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import org.apache.fineract.notification.domain.TopicSubscriber;
-import org.apache.fineract.notification.domain.TopicSubscriberRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service
-public class TopicSubscriberWritePlatformServiceJpaRepositoryImpl implements TopicSubscriberWritePlatformService {
-
-    private final TopicSubscriberRepository topicSubscriberRepository;
-
-    @Autowired
-    public TopicSubscriberWritePlatformServiceJpaRepositoryImpl(TopicSubscriberRepository topicSubscriberRepository) {
-        this.topicSubscriberRepository = topicSubscriberRepository;
-    }
-
-    @Override
-    public Long create(TopicSubscriber topicSubscriber) {
-        topicSubscriberRepository.saveAndFlush(topicSubscriber);
-        return topicSubscriber.getId();
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformService.java
deleted file mode 100644
index dfc09e611..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformService.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import org.apache.fineract.notification.domain.Topic;
-
-public interface TopicWritePlatformService {
-
-    Long create(Topic topic);
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformServiceJpaRepositoryImpl.java
deleted file mode 100644
index 73c0d656c..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/TopicWritePlatformServiceJpaRepositoryImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.notification.service;
-
-import org.apache.fineract.notification.domain.Topic;
-import org.apache.fineract.notification.domain.TopicRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service
-public class TopicWritePlatformServiceJpaRepositoryImpl implements TopicWritePlatformService {
-
-    private final TopicRepository topicRepository;
-
-    @Autowired
-    public TopicWritePlatformServiceJpaRepositoryImpl(TopicRepository topicRepository) {
-        this.topicRepository = topicRepository;
-    }
-
-    @Override
-    public Long create(Topic topic) {
-        topicRepository.saveAndFlush(topic);
-        return topic.getId();
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
index 995432259..ebdb2ebe6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java
@@ -20,6 +20,8 @@ package org.apache.fineract.organisation.office.service;
 
 import java.util.Map;
 import javax.persistence.PersistenceException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -27,7 +29,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
 import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
 import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import org.apache.fineract.notification.service.TopicDomainService;
 import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
 import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -39,9 +40,6 @@ import org.apache.fineract.organisation.office.domain.OfficeTransactionRepositor
 import org.apache.fineract.organisation.office.serialization.OfficeCommandFromApiJsonDeserializer;
 import org.apache.fineract.organisation.office.serialization.OfficeTransactionCommandFromApiJsonDeserializer;
 import org.apache.fineract.useradministration.domain.AppUser;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Caching;
 import org.springframework.dao.DataIntegrityViolationException;
@@ -50,32 +48,16 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@Slf4j
+@RequiredArgsConstructor
 public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWritePlatformService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(OfficeWritePlatformServiceJpaRepositoryImpl.class);
-
     private final PlatformSecurityContext context;
     private final OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer;
     private final OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer;
     private final OfficeRepositoryWrapper officeRepositoryWrapper;
     private final OfficeTransactionRepository officeTransactionRepository;
     private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
-    private final TopicDomainService topicDomainService;
-
-    @Autowired
-    public OfficeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
-            final OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer,
-            final OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer,
-            final OfficeRepositoryWrapper officeRepositoryWrapper, final OfficeTransactionRepository officeMonetaryTransferRepository,
-            final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final TopicDomainService topicDomainService) {
-        this.context = context;
-        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
-        this.moneyTransferCommandFromApiJsonDeserializer = moneyTransferCommandFromApiJsonDeserializer;
-        this.officeRepositoryWrapper = officeRepositoryWrapper;
-        this.officeTransactionRepository = officeMonetaryTransferRepository;
-        this.applicationCurrencyRepository = applicationCurrencyRepository;
-        this.topicDomainService = topicDomainService;
-    }
 
     @Transactional
     @Override
@@ -104,8 +86,6 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP
 
             this.officeRepositoryWrapper.save(office);
 
-            this.topicDomainService.createTopic(office);
-
             return new CommandProcessingResultBuilder() //
                     .withCommandId(command.commandId()) //
                     .withEntityId(office.getId()) //
@@ -150,8 +130,6 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP
 
             if (!changes.isEmpty()) {
                 this.officeRepositoryWrapper.saveAndFlush(office);
-
-                this.topicDomainService.updateTopic(office, changes);
             }
 
             return new CommandProcessingResultBuilder() //
@@ -239,7 +217,7 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP
                     "name", name);
         }
 
-        LOG.error("Error occured.", dve);
+        log.error("Error occured.", dve);
         throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue",
                 "Unknown data integrity issue with resource.");
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResourceSwagger.java
index 4d53e1da1..f1564222f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResourceSwagger.java
@@ -20,6 +20,7 @@ package org.apache.fineract.useradministration.api;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.Collection;
+import java.util.List;
 import org.apache.fineract.organisation.office.data.OfficeData;
 import org.apache.fineract.organisation.staff.data.StaffData;
 import org.apache.fineract.useradministration.data.RoleData;
@@ -111,6 +112,10 @@ final class UsersApiResourceSwagger {
 
         @Schema(example = "newuser")
         public String username;
+        @Schema(example = "password")
+        public String password;
+        @Schema(example = "repeatPassword")
+        public String repeatPassword;
         @Schema(example = "Test")
         public String firstname;
         @Schema(example = "User")
@@ -122,7 +127,7 @@ final class UsersApiResourceSwagger {
         @Schema(example = "1")
         public Long staffId;
         @Schema(example = "[2,3]")
-        public String roles;
+        public List<String> roles;
         @Schema(example = "true")
         public Boolean sendPasswordToEmail;
         @Schema(example = "true")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
index 9f076e2bd..6fce5360b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.useradministration.domain;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
@@ -598,7 +599,11 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
         return !hasAnyPermission(permissions);
     }
 
-    private boolean hasAnyPermission(final List<String> permissions) {
+    public boolean hasAnyPermission(String... permissions) {
+        return hasAnyPermission(Arrays.asList(permissions));
+    }
+
+    public boolean hasAnyPermission(final List<String> permissions) {
         boolean hasAtLeastOneOf = false;
 
         for (final String permissionCode : permissions) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java
index 48d3479ca..f7481d7bb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUserRepository.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.useradministration.domain;
 
+import java.util.Collection;
 import org.apache.fineract.infrastructure.security.domain.PlatformUserRepository;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -28,4 +29,6 @@ public interface AppUserRepository extends JpaRepository<AppUser, Long>, JpaSpec
 
     @Query("Select appUser from AppUser appUser where appUser.username = :username")
     AppUser findAppUserByName(@Param("username") String username);
+
+    Collection<AppUser> findByOfficeId(Long officeId);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java
index 607ac06e3..8dd4ee836 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java
@@ -26,6 +26,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.persistence.PersistenceException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -37,7 +39,6 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx
 import org.apache.fineract.infrastructure.core.service.PlatformEmailSendException;
 import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import org.apache.fineract.notification.service.TopicDomainService;
 import org.apache.fineract.organisation.office.domain.Office;
 import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
 import org.apache.fineract.organisation.staff.domain.Staff;
@@ -55,9 +56,6 @@ import org.apache.fineract.useradministration.domain.UserDomainService;
 import org.apache.fineract.useradministration.exception.PasswordPreviouslyUsedException;
 import org.apache.fineract.useradministration.exception.RoleNotFoundException;
 import org.apache.fineract.useradministration.exception.UserNotFoundException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Caching;
 import org.springframework.dao.DataIntegrityViolationException;
@@ -70,10 +68,10 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.ObjectUtils;
 
 @Service
+@Slf4j
+@RequiredArgsConstructor
 public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWritePlatformService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(AppUserWritePlatformServiceJpaRepositoryImpl.class);
-
     private final PlatformSecurityContext context;
     private final UserDomainService userDomainService;
     private final PlatformPasswordEncoder platformPasswordEncoder;
@@ -84,27 +82,6 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
     private final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository;
     private final StaffRepositoryWrapper staffRepositoryWrapper;
     private final ClientRepositoryWrapper clientRepositoryWrapper;
-    private final TopicDomainService topicDomainService;
-
-    @Autowired
-    public AppUserWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AppUserRepository appUserRepository,
-            final UserDomainService userDomainService, final OfficeRepositoryWrapper officeRepositoryWrapper,
-            final RoleRepository roleRepository, final PlatformPasswordEncoder platformPasswordEncoder,
-            final UserDataValidator fromApiJsonDeserializer, final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository,
-            final StaffRepositoryWrapper staffRepositoryWrapper, final ClientRepositoryWrapper clientRepositoryWrapper,
-            final TopicDomainService topicDomainService) {
-        this.context = context;
-        this.appUserRepository = appUserRepository;
-        this.userDomainService = userDomainService;
-        this.officeRepositoryWrapper = officeRepositoryWrapper;
-        this.roleRepository = roleRepository;
-        this.platformPasswordEncoder = platformPasswordEncoder;
-        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
-        this.appUserPreviewPasswordRepository = appUserPreviewPasswordRepository;
-        this.staffRepositoryWrapper = staffRepositoryWrapper;
-        this.clientRepositoryWrapper = clientRepositoryWrapper;
-        this.topicDomainService = topicDomainService;
-    }
 
     @Override
     @Transactional
@@ -152,8 +129,6 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
             final Boolean sendPasswordToEmail = command.booleanObjectValueOfParameterNamed("sendPasswordToEmail");
             this.userDomainService.create(appUser, sendPasswordToEmail);
 
-            this.topicDomainService.subscribeUserToTopic(appUser);
-
             return new CommandProcessingResultBuilder() //
                     .withCommandId(command.commandId()) //
                     .withEntityId(appUser.getId()) //
@@ -162,11 +137,11 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
         } catch (final DataIntegrityViolationException dve) {
             throw handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
         } catch (final JpaSystemException | PersistenceException | AuthenticationServiceException dve) {
-            LOG.error("createUser: JpaSystemException | PersistenceException | AuthenticationServiceException", dve);
+            log.error("createUser: JpaSystemException | PersistenceException | AuthenticationServiceException", dve);
             Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
             throw handleDataIntegrityIssues(command, throwable, dve);
         } catch (final PlatformEmailSendException e) {
-            LOG.error("createUser: PlatformEmailSendException", e);
+            log.error("createUser: PlatformEmailSendException", e);
 
             final String email = command.stringValueOfParameterNamed("email");
             final ApiParameterError error = ApiParameterError.parameterError("error.msg.user.email.invalid",
@@ -208,7 +183,6 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
 
             final Map<String, Object> changes = userToUpdate.update(command, this.platformPasswordEncoder, clients);
 
-            this.topicDomainService.updateUserSubscription(userToUpdate, changes);
             if (changes.containsKey("officeId")) {
                 final Long officeId = (Long) changes.get("officeId");
                 final Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
@@ -248,7 +222,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
         } catch (final DataIntegrityViolationException dve) {
             throw handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
         } catch (final JpaSystemException | PersistenceException | AuthenticationServiceException dve) {
-            LOG.error("updateUser: JpaSystemException | PersistenceException | AuthenticationServiceException", dve);
+            log.error("updateUser: JpaSystemException | PersistenceException | AuthenticationServiceException", dve);
             Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
             throw handleDataIntegrityIssues(command, throwable, dve);
         }
@@ -303,7 +277,6 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
         }
 
         user.delete();
-        this.topicDomainService.unsubcribeUserFromTopic(user);
         this.appUserRepository.save(user);
 
         return new CommandProcessingResultBuilder().withEntityId(userId).withOfficeId(user.getOffice().getId()).build();
@@ -333,7 +306,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
                     "Self Service User Id is already created. Go to Admin->Users to edit or delete the self-service user.");
         }
 
-        LOG.error("handleDataIntegrityIssues: Neither duplicate username nor existing user; unknown error occured", dve);
+        log.error("handleDataIntegrityIssues: Neither duplicate username nor existing user; unknown error occured", dve);
         return new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java
index a35bc5740..0a7148482 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java
@@ -22,13 +22,14 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import javax.persistence.PersistenceException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import org.apache.fineract.notification.service.TopicDomainService;
 import org.apache.fineract.useradministration.command.PermissionsCommand;
 import org.apache.fineract.useradministration.domain.Permission;
 import org.apache.fineract.useradministration.domain.PermissionRepository;
@@ -38,9 +39,6 @@ import org.apache.fineract.useradministration.exception.PermissionNotFoundExcept
 import org.apache.fineract.useradministration.exception.RoleAssociatedException;
 import org.apache.fineract.useradministration.exception.RoleNotFoundException;
 import org.apache.fineract.useradministration.serialization.PermissionsCommandFromApiJsonDeserializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Caching;
 import org.springframework.dao.DataIntegrityViolationException;
@@ -49,27 +47,15 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@Slf4j
+@RequiredArgsConstructor
 public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatformService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(RoleWritePlatformServiceJpaRepositoryImpl.class);
     private final PlatformSecurityContext context;
     private final RoleRepository roleRepository;
     private final PermissionRepository permissionRepository;
     private final RoleDataValidator roleCommandFromApiJsonDeserializer;
     private final PermissionsCommandFromApiJsonDeserializer permissionsFromApiJsonDeserializer;
-    private final TopicDomainService topicDomainService;
-
-    @Autowired
-    public RoleWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final RoleRepository roleRepository,
-            final PermissionRepository permissionRepository, final RoleDataValidator roleCommandFromApiJsonDeserializer,
-            final PermissionsCommandFromApiJsonDeserializer fromApiJsonDeserializer, final TopicDomainService topicDomainService) {
-        this.context = context;
-        this.roleRepository = roleRepository;
-        this.permissionRepository = permissionRepository;
-        this.roleCommandFromApiJsonDeserializer = roleCommandFromApiJsonDeserializer;
-        this.permissionsFromApiJsonDeserializer = fromApiJsonDeserializer;
-        this.topicDomainService = topicDomainService;
-    }
 
     @Transactional
     @Override
@@ -81,9 +67,7 @@ public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatf
             this.roleCommandFromApiJsonDeserializer.validateForCreate(command.json());
 
             final Role entity = Role.fromJson(command);
-            this.roleRepository.save(entity);
-
-            this.topicDomainService.createTopic(entity);
+            this.roleRepository.saveAndFlush(entity);
 
             return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entity.getId()).build();
         } catch (final JpaSystemException | DataIntegrityViolationException dve) {
@@ -118,7 +102,7 @@ public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatf
     }
 
     private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
-        LOG.error("Error occured.", dve);
+        log.error("Error occured.", dve);
     }
 
     @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) })
@@ -136,9 +120,6 @@ public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatf
             final Map<String, Object> changes = role.update(command);
             if (!changes.isEmpty()) {
                 this.roleRepository.saveAndFlush(role);
-                if (changes.containsKey("name")) {
-                    this.topicDomainService.updateTopic(previousRoleName, role, changes);
-                }
             }
 
             return new CommandProcessingResultBuilder() //
@@ -230,8 +211,6 @@ public class RoleWritePlatformServiceJpaRepositoryImpl implements RoleWritePlatf
                 throw new RoleAssociatedException("error.msg.role.associated.with.users.deleted", roleId);
             }
 
-            this.topicDomainService.deleteTopic(role);
-
             this.roleRepository.delete(role);
             return new CommandProcessingResultBuilder().withEntityId(roleId).build();
         } catch (final JpaSystemException | DataIntegrityViolationException e) {
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 0eed83638..079c5e474 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -32,4 +32,5 @@
     <include file="parts/0010_lien_allowed_on_savings_account_products.xml" relativeToChangelogFile="true"/>
     <include file="parts/0011_add_credit_balance_refund_permission.xml" relativeToChangelogFile="true"/>
     <include file="parts/0012_add_merchantissuedrefund_payoutrefund_goodwillcredit_permissions.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0013_remove_topics.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0013_remove_topics.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0013_remove_topics.xml
new file mode 100644
index 000000000..32a1f6727
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0013_remove_topics.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+    <changeSet author="fineract" id="1">
+        <dropTable tableName="topic_subscriber"/>
+        <dropTable tableName="topic"/>
+    </changeSet>
+</databaseChangeLog>
diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle
index 320ff5d83..486299f29 100644
--- a/integration-tests/build.gradle
+++ b/integration-tests/build.gradle
@@ -29,7 +29,7 @@ configurations {
 apply from: 'dependencies.gradle'
 
 // enable when all tests are migrated
-tasks.cucumber.onlyIf {false}
+tasks.cucumber.onlyIf { false }
 
 // Allow external drivers to be used for the tests without packaging it
 // mainly due to license incompatibilities
@@ -92,7 +92,7 @@ cargoStartLocal.dependsOn ':fineract-war:war'
 cargoStartLocal.mustRunAfter 'testClasses'
 
 test {
-    dependsOn (cargoStartLocal)
+    dependsOn(cargoStartLocal)
     finalizedBy cargoStopLocal
 }
 
diff --git a/integration-tests/dependencies.gradle b/integration-tests/dependencies.gradle
index da8ed8507..8067dae52 100644
--- a/integration-tests/dependencies.gradle
+++ b/integration-tests/dependencies.gradle
@@ -38,6 +38,7 @@ dependencies {
         exclude group: 'org.apache.sling'
         exclude group: 'com.sun.xml.bind'
     }
+    testImplementation 'org.awaitility:awaitility'
 
     testCompileOnly 'org.projectlombok:lombok'
     testAnnotationProcessor 'org.projectlombok:lombok'
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotificationApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotificationApiTest.java
index efcb9e174..def1ca8b0 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotificationApiTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotificationApiTest.java
@@ -18,40 +18,93 @@
  */
 package org.apache.fineract.integrationtests;
 
+import static org.apache.fineract.integrationtests.useradministration.roles.RolesHelper.SUPER_USER_ROLE_ID;
+
 import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.builder.ResponseSpecBuilder;
 import io.restassured.http.ContentType;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
-import java.util.HashMap;
+import java.util.List;
+import org.apache.fineract.client.models.GetNotification;
+import org.apache.fineract.client.models.GetNotificationsResponse;
+import org.apache.fineract.client.models.GetOfficesResponse;
+import org.apache.fineract.client.models.PostClientsRequest;
+import org.apache.fineract.client.models.PostUsersRequest;
+import org.apache.fineract.client.models.PostUsersResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.NotificationHelper;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class NotificationApiTest {
 
-    private static final Logger LOG = LoggerFactory.getLogger(NotificationApiTest.class);
-    private ResponseSpecification responseSpec;
+    public static final int SUPER_USER_ID = 1;
+    public static final String CLIENT_OBJECT_TYPE = "client";
+    public static final String CREATED_ACTION_TYPE = "created";
     private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+
+    private RequestSpecification newUserRequestSpec;
+    private ResponseSpecification newUserResponseSpec;
 
     @BeforeEach
     public void setUp() {
         Utils.initializeRESTAssured();
-        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
-        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
-        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec);
+        String username = Utils.randomNameGenerator("NotificationUser", 4);
+        String password = Utils.randomStringGenerator("aA1", 10); // prefix is to conform with the password rules
+        PostUsersRequest createUserRequest = new PostUsersRequest().username(username)
+                .firstname(Utils.randomNameGenerator("NotificationFN", 4)).lastname(Utils.randomNameGenerator("NotificationLN", 4))
+                .email("whatever@mifos.org").password(password).repeatPassword(password).sendPasswordToEmail(false)
+                .roles(List.of(Long.toString(SUPER_USER_ROLE_ID))).officeId(headOffice.getId());
+
+        PostUsersResponse userCreationResponse = UserHelper.createUser(requestSpec, responseSpec, createUserRequest);
+        Assertions.assertNotNull(userCreationResponse.getResourceId());
+
+        newUserRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        newUserRequestSpec.header("Authorization",
+                "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, password));
+        newUserResponseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+    }
+
+    @Test
+    public void testNotificationRetrievalWorksWhenNoNotificationsAreAvailable() {
+        // given
+        // when
+        GetNotificationsResponse response = NotificationHelper.getNotifications(requestSpec, responseSpec);
+        // then
+        Assertions.assertNotNull(response);
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    public void testNotificationRetrieval() {
-        HashMap<String, Object> response = (HashMap<String, Object>) NotificationHelper.getNotifications(this.requestSpec,
-                this.responseSpec, "");
-        LOG.info("Response : {}", response.toString());
+    public void testNotificationRetrievalWorksWhenOneNotificationIsAvailable() {
+        // given
+        PostClientsRequest clientRequest = ClientHelper.defaultClientCreationRequest();
+        Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, clientRequest);
+        Assertions.assertNotNull(clientId);
+
+        // when
+        NotificationHelper.waitUntilNotificationsAreAvailable(newUserRequestSpec, newUserResponseSpec);
+        GetNotificationsResponse response = NotificationHelper.getNotifications(newUserRequestSpec, newUserResponseSpec);
+        // then
         Assertions.assertNotNull(response);
+        List<GetNotification> pageItems = response.getPageItems();
+        Assertions.assertEquals(1, pageItems.size());
+        GetNotification firstNotification = pageItems.get(0);
+        Assertions.assertEquals(SUPER_USER_ID, firstNotification.getActorId());
+        Assertions.assertEquals(false, firstNotification.getIsRead());
+        Assertions.assertEquals(CREATED_ACTION_TYPE, firstNotification.getAction());
+        Assertions.assertEquals(clientId.longValue(), firstNotification.getObjectId());
+        Assertions.assertEquals(CLIENT_OBJECT_TYPE, firstNotification.getObjectType());
     }
 }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotificationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotificationHelper.java
index e375059f2..9c6487d16 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotificationHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotificationHelper.java
@@ -18,28 +18,40 @@
  */
 package org.apache.fineract.integrationtests.common;
 
+import static org.awaitility.Awaitility.await;
+
+import com.google.gson.Gson;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class NotificationHelper {
+import java.time.Duration;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetNotificationsResponse;
+import org.apache.fineract.client.util.JSON;
 
-    private static final Logger LOG = LoggerFactory.getLogger(NotificationHelper.class);
-    private final RequestSpecification requestSpec;
-    private final ResponseSpecification responseSpec;
+@Slf4j
+public final class NotificationHelper {
 
     private static final String NOTIFICATION_API_URL = "/fineract-provider/api/v1/notifications?" + Utils.TENANT_IDENTIFIER;
+    private static final Gson GSON = new JSON().getGson();
 
-    public NotificationHelper(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
-        this.requestSpec = requestSpec;
-        this.responseSpec = responseSpec;
-    }
+    private NotificationHelper() {}
 
-    public static Object getNotifications(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
-            final String jsonReturn) {
+    public static GetNotificationsResponse getNotifications(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
         final String GET_NOTIFICATIONS_URL = NOTIFICATION_API_URL;
-        LOG.info("-----------------------------GET NOTIFICATIONS-----------------------------------");
-        return Utils.performServerGet(requestSpec, responseSpec, GET_NOTIFICATIONS_URL, "");
+        log.info("-----------------------------GET NOTIFICATIONS-----------------------------------");
+        String response = Utils.performServerGet(requestSpec, responseSpec, GET_NOTIFICATIONS_URL);
+        return GSON.fromJson(response, GetNotificationsResponse.class);
+    }
+
+    public static boolean areNotificationsAvailable(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return getNotifications(requestSpec, responseSpec).getPageItems().size() > 0;
+    }
+
+    // Waiting for notifications to be available is needed due to the asynchronous event processing
+    public static void waitUntilNotificationsAreAvailable(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        await().atMost(Duration.ofSeconds(30)).pollInterval(Duration.ofMillis(250))
+                .until(() -> NotificationHelper.areNotificationsAvailable(requestSpec, responseSpec));
     }
 }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java
index f6f5faf1c..3c4cb6d70 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java
@@ -29,6 +29,8 @@ import java.io.InputStream;
 import java.util.HashMap;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
+import org.apache.fineract.client.models.GetOfficesResponse;
+import org.apache.fineract.client.util.JSON;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.slf4j.Logger;
@@ -36,8 +38,11 @@ import org.slf4j.LoggerFactory;
 
 public class OfficeHelper {
 
+    public static final long HEAD_OFFICE_ID = 1L; // The ID is hardcoded in the initial Liquibase migration script
+
     private static final Logger LOG = LoggerFactory.getLogger(OfficeHelper.class);
     private static final String OFFICE_URL = "/fineract-provider/api/v1/offices";
+    private static final Gson GSON = new JSON().getGson();
     private final RequestSpecification requestSpec;
     private final ResponseSpecification responseSpec;
 
@@ -52,6 +57,12 @@ public class OfficeHelper {
         return new Gson().fromJson(json, new TypeToken<OfficeDomain>() {}.getType());
     }
 
+    public static GetOfficesResponse getHeadOffice(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        String response = Utils.performServerGet(requestSpec, responseSpec,
+                OFFICE_URL + "/" + HEAD_OFFICE_ID + "?" + Utils.TENANT_IDENTIFIER);
+        return GSON.fromJson(response, GetOfficesResponse.class);
+    }
+
     public Integer createOffice(final String openingDate) {
         String json = getAsJSON(openingDate);
         return Utils.performServerPost(this.requestSpec, this.responseSpec, OFFICE_URL + "?" + Utils.TENANT_IDENTIFIER, json,
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
index 4f6bef4a0..9d7bb3b4f 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
@@ -118,12 +118,17 @@ public final class Utils {
     }
 
     public static String loginIntoServerAndGetBase64EncodedAuthenticationKey() {
+        return loginIntoServerAndGetBase64EncodedAuthenticationKey("mifos", "password");
+    }
+
+    public static String loginIntoServerAndGetBase64EncodedAuthenticationKey(String username, String password) {
         awaitSpringBootActuatorHealthyUp();
         try {
             LOG.info("Logging in, for integration test...");
             // system.out.println("-----------------------------------LOGIN-----------------------------------------");
-            String json = RestAssured.given().contentType(ContentType.JSON).body("{\"username\":\"mifos\", \"password\":\"password\"}")
-                    .expect().log().ifError().when().post(LOGIN_URL).asString();
+            String json = RestAssured.given().contentType(ContentType.JSON)
+                    .body("{\"username\":\"" + username + "\", \"password\":\"" + password + "\"}").expect().log().ifError().when()
+                    .post(LOGIN_URL).asString();
             assertThat("Failed to login into fineract platform", StringUtils.isBlank(json), is(false));
             String key = JsonPath.with(json).get("base64EncodedAuthenticationKey");
             assertThat("Failed to obtain key: " + json, StringUtils.isBlank(key), is(false));
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java
index def8c7601..024957173 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java
@@ -26,6 +26,8 @@ import org.apache.fineract.integrationtests.common.Utils;
 
 public final class RolesHelper {
 
+    public static final long SUPER_USER_ROLE_ID = 1L; // This is hardcoded into the initial Liquibase migration
+
     private RolesHelper() {
 
     }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
index f00797586..1a86dca53 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
@@ -18,22 +18,25 @@
  */
 package org.apache.fineract.integrationtests.useradministration.users;
 
+import com.google.gson.Gson;
 import io.restassured.path.json.JsonPath;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.client.models.PostUsersRequest;
+import org.apache.fineract.client.models.PostUsersResponse;
+import org.apache.fineract.client.util.JSON;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.junit.jupiter.api.Assertions;
 
 public final class UserHelper {
 
-    private UserHelper() {
-
-    }
-
     private static final String CREATE_USER_URL = "/fineract-provider/api/v1/users?" + Utils.TENANT_IDENTIFIER;
     private static final String USER_URL = "/fineract-provider/api/v1/users";
+    private static final Gson GSON = new JSON().getGson();
+
+    private UserHelper() {}
 
     public static Integer createUser(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, int roleId,
             int staffId) {
@@ -46,6 +49,13 @@ public final class UserHelper {
                 attribute);
     }
 
+    public static PostUsersResponse createUser(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            PostUsersRequest request) {
+        String requestBody = GSON.toJson(request);
+        String response = Utils.performServerPost(requestSpec, responseSpec, CREATE_USER_URL, requestBody);
+        return GSON.fromJson(response, PostUsersResponse.class);
+    }
+
     public static Object createUserForSelfService(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
             int roleId, int staffId, int clientId, String attribute) {
         return Utils.performServerPost(requestSpec, responseSpec, CREATE_USER_URL,