You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/01/13 11:48:38 UTC

(polaris) branch main updated: Flatten events hierarchy (#3293)

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

adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new ce627f9b9 Flatten events hierarchy (#3293)
ce627f9b9 is described below

commit ce627f9b96476b1bb76abc6806a859ffcb920a9b
Author: Oleg Soloviov <40...@users.noreply.github.com>
AuthorDate: Tue Jan 13 11:48:34 2026 +0000

    Flatten events hierarchy (#3293)
    
    Co-authored-by: Alexandre Dutra <ad...@apache.org>
---
 .../PolarisCatalogsEventServiceDelegator.java      | 275 ++++---
 ...PolarisPrincipalRolesEventServiceDelegator.java | 172 +++--
 .../PolarisPrincipalsEventServiceDelegator.java    | 179 +++--
 .../CatalogGenericTableEventServiceDelegator.java  |  97 ++-
 .../service/catalog/iceberg/IcebergCatalog.java    |  69 +-
 .../IcebergRestCatalogEventServiceDelegator.java   | 627 ++++++++++------
 ...bergRestConfigurationEventServiceDelegator.java |  23 +-
 .../policy/CatalogPolicyEventServiceDelegator.java | 209 ++++--
 .../service/events/AfterAttemptTaskEvent.java      |  36 -
 .../service/events/AllowedAttributeTypes.java      | 123 ++++
 .../polaris/service/events/AttributeKey.java       |  57 ++
 .../polaris/service/events/AttributeMap.java       |  66 ++
 .../service/events/BeforeAttemptTaskEvent.java     |  34 -
 .../events/BeforeLimitRequestRateEvent.java        |  33 -
 .../events/CatalogGenericTableServiceEvents.java   |  98 ---
 .../service/events/CatalogPolicyServiceEvents.java | 230 ------
 .../service/events/CatalogsServiceEvents.java      | 322 --------
 .../polaris/service/events/EventAttributes.java    | 233 ++++++
 .../service/events/IcebergRestCatalogEvents.java   | 648 ----------------
 .../polaris/service/events/PolarisEvent.java       |  16 +-
 .../events/PrincipalRolesServiceEvents.java        | 192 -----
 .../service/events/PrincipalsServiceEvents.java    | 191 -----
 .../PropertyMapEventListener.java                  |  46 --
 .../aws/cloudwatch/AwsCloudWatchEventListener.java |  31 +-
 .../events/listeners/PolarisEventListener.java     | 406 +---------
 .../listeners/PolarisPersistenceEventListener.java |  61 +-
 .../service/ratelimiter/RateLimiterFilter.java     |  16 +-
 .../polaris/service/task/TaskExecutorImpl.java     |  27 +-
 .../iceberg/AbstractIcebergCatalogTest.java        |  22 +-
 .../iceberg/AbstractIcebergCatalogViewTest.java    |  67 +-
 .../iceberg/CommitTransactionEventTest.java        |  47 +-
 .../polaris/service/events/AttributeKeyTest.java   |  98 +++
 .../polaris/service/events/AttributeMapTest.java   | 125 ++++
 .../polaris/service/events/PolarisEventTest.java   |  59 ++
 .../cloudwatch/AwsCloudWatchEventListenerTest.java |  31 +-
 .../events/listeners/PolarisEventListenerTest.java |  85 ---
 ...InMemoryBufferEventListenerIntegrationTest.java |   4 +-
 .../service/ratelimiter/RateLimiterFilterTest.java |  10 +-
 .../polaris/service/task/TaskExecutorImplTest.java |  30 +-
 .../events/listeners/TestPolarisEventListener.java | 819 +--------------------
 40 files changed, 2084 insertions(+), 3830 deletions(-)

diff --git a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisCatalogsEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisCatalogsEventServiceDelegator.java
index 2661f966c..cc0e59fd4 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisCatalogsEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisCatalogsEventServiceDelegator.java
@@ -42,8 +42,11 @@ import org.apache.polaris.core.admin.model.ViewGrant;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.core.entity.PolarisPrivilege;
 import org.apache.polaris.service.admin.api.PolarisCatalogsApiService;
-import org.apache.polaris.service.events.CatalogsServiceEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 
 @Decorator
@@ -57,39 +60,51 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
   @Override
   public Response createCatalog(
       CreateCatalogRequest request, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeCreateCatalog(
-        new CatalogsServiceEvents.BeforeCreateCatalogEvent(
-            eventMetadataFactory.create(), request.getCatalog().getName()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, request.getCatalog().getName())));
     Response resp = delegate.createCatalog(request, realmContext, securityContext);
-    polarisEventListener.onAfterCreateCatalog(
-        new CatalogsServiceEvents.AfterCreateCatalogEvent(
-            eventMetadataFactory.create(), (Catalog) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG, (Catalog) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response deleteCatalog(
       String catalogName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeDeleteCatalog(
-        new CatalogsServiceEvents.BeforeDeleteCatalogEvent(
-            eventMetadataFactory.create(), catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DELETE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, catalogName)));
     Response resp = delegate.deleteCatalog(catalogName, realmContext, securityContext);
-    polarisEventListener.onAfterDeleteCatalog(
-        new CatalogsServiceEvents.AfterDeleteCatalogEvent(
-            eventMetadataFactory.create(), catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DELETE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, catalogName)));
     return resp;
   }
 
   @Override
   public Response getCatalog(
       String catalogName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeGetCatalog(
-        new CatalogsServiceEvents.BeforeGetCatalogEvent(
-            eventMetadataFactory.create(), catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, catalogName)));
     Response resp = delegate.getCatalog(catalogName, realmContext, securityContext);
-    polarisEventListener.onAfterGetCatalog(
-        new CatalogsServiceEvents.AfterGetCatalogEvent(
-            eventMetadataFactory.create(), (Catalog) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG, (Catalog) resp.getEntity())));
     return resp;
   }
 
@@ -99,24 +114,36 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       UpdateCatalogRequest updateRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeUpdateCatalog(
-        new CatalogsServiceEvents.BeforeUpdateCatalogEvent(
-            eventMetadataFactory.create(), catalogName, updateRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.UPDATE_CATALOG_REQUEST, updateRequest)));
     Response resp =
         delegate.updateCatalog(catalogName, updateRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdateCatalog(
-        new CatalogsServiceEvents.AfterUpdateCatalogEvent(
-            eventMetadataFactory.create(), (Catalog) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_CATALOG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG, (Catalog) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response listCatalogs(RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeListCatalogs(
-        new CatalogsServiceEvents.BeforeListCatalogsEvent(eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_CATALOGS,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     Response resp = delegate.listCatalogs(realmContext, securityContext);
-    polarisEventListener.onAfterListCatalogs(
-        new CatalogsServiceEvents.AfterListCatalogsEvent(eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_CATALOGS,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     return resp;
   }
 
@@ -126,13 +153,21 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       CreateCatalogRoleRequest request,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeCreateCatalogRole(
-        new CatalogsServiceEvents.BeforeCreateCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, request.getCatalogRole().getName()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, request.getCatalogRole().getName())));
     Response resp = delegate.createCatalogRole(catalogName, request, realmContext, securityContext);
-    polarisEventListener.onAfterCreateCatalogRole(
-        new CatalogsServiceEvents.AfterCreateCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, (CatalogRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE, (CatalogRole) resp.getEntity())));
     return resp;
   }
 
@@ -142,14 +177,22 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       String catalogRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeDeleteCatalogRole(
-        new CatalogsServiceEvents.BeforeDeleteCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DELETE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     Response resp =
         delegate.deleteCatalogRole(catalogName, catalogRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterDeleteCatalogRole(
-        new CatalogsServiceEvents.AfterDeleteCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DELETE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     return resp;
   }
 
@@ -159,14 +202,22 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       String catalogRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeGetCatalogRole(
-        new CatalogsServiceEvents.BeforeGetCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     Response resp =
         delegate.getCatalogRole(catalogName, catalogRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterGetCatalogRole(
-        new CatalogsServiceEvents.AfterGetCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, (CatalogRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE, (CatalogRole) resp.getEntity())));
     return resp;
   }
 
@@ -177,28 +228,41 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       UpdateCatalogRoleRequest updateRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeUpdateCatalogRole(
-        new CatalogsServiceEvents.BeforeUpdateCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName, updateRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)
+                .put(EventAttributes.UPDATE_CATALOG_ROLE_REQUEST, updateRequest)));
     Response resp =
         delegate.updateCatalogRole(
             catalogName, catalogRoleName, updateRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdateCatalogRole(
-        new CatalogsServiceEvents.AfterUpdateCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, (CatalogRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE, (CatalogRole) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response listCatalogRoles(
       String catalogName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onAfterListCatalogRoles(
-        new CatalogsServiceEvents.AfterListCatalogRolesEvent(
-            eventMetadataFactory.create(), catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_CATALOG_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, catalogName)));
     Response resp = delegate.listCatalogRoles(catalogName, realmContext, securityContext);
-    polarisEventListener.onBeforeListCatalogRoles(
-        new CatalogsServiceEvents.BeforeListCatalogRolesEvent(
-            eventMetadataFactory.create(), catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_CATALOG_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CATALOG_NAME, catalogName)));
     return resp;
   }
 
@@ -209,20 +273,27 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       AddGrantRequest grantRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeAddGrantToCatalogRole(
-        new CatalogsServiceEvents.BeforeAddGrantToCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName, grantRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ADD_GRANT_TO_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)
+                .put(EventAttributes.ADD_GRANT_REQUEST, grantRequest)));
     Response resp =
         delegate.addGrantToCatalogRole(
             catalogName, catalogRoleName, grantRequest, realmContext, securityContext);
     GrantResource grantResource = grantRequest.getGrant();
-    polarisEventListener.onAfterAddGrantToCatalogRole(
-        new CatalogsServiceEvents.AfterAddGrantToCatalogRoleEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_ADD_GRANT_TO_CATALOG_ROLE,
             eventMetadataFactory.create(),
-            catalogName,
-            catalogRoleName,
-            getPrivilegeFromGrantResource(grantResource),
-            grantResource));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)
+                .put(EventAttributes.PRIVILEGE, getPrivilegeFromGrantResource(grantResource))
+                .put(EventAttributes.GRANT_RESOURCE, grantResource)));
     return resp;
   }
 
@@ -234,21 +305,29 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       RevokeGrantRequest grantRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeRevokeGrantFromCatalogRole(
-        new CatalogsServiceEvents.BeforeRevokeGrantFromCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName, grantRequest, cascade));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REVOKE_GRANT_FROM_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)
+                .put(EventAttributes.REVOKE_GRANT_REQUEST, grantRequest)
+                .put(EventAttributes.CASCADE, cascade)));
     Response resp =
         delegate.revokeGrantFromCatalogRole(
             catalogName, catalogRoleName, cascade, grantRequest, realmContext, securityContext);
     GrantResource grantResource = grantRequest.getGrant();
-    polarisEventListener.onAfterRevokeGrantFromCatalogRole(
-        new CatalogsServiceEvents.AfterRevokeGrantFromCatalogRoleEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REVOKE_GRANT_FROM_CATALOG_ROLE,
             eventMetadataFactory.create(),
-            catalogName,
-            catalogRoleName,
-            getPrivilegeFromGrantResource(grantResource),
-            grantResource,
-            cascade));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)
+                .put(EventAttributes.PRIVILEGE, getPrivilegeFromGrantResource(grantResource))
+                .put(EventAttributes.GRANT_RESOURCE, grantResource)
+                .put(EventAttributes.CASCADE, cascade)));
     return resp;
   }
 
@@ -258,15 +337,23 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       String catalogRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeListAssigneePrincipalRolesForCatalogRole(
-        new CatalogsServiceEvents.BeforeListAssigneePrincipalRolesForCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_ASSIGNEE_PRINCIPAL_ROLES_FOR_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     Response resp =
         delegate.listAssigneePrincipalRolesForCatalogRole(
             catalogName, catalogRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterListAssigneePrincipalRolesForCatalogRole(
-        new CatalogsServiceEvents.AfterListAssigneePrincipalRolesForCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_ASSIGNEE_PRINCIPAL_ROLES_FOR_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     return resp;
   }
 
@@ -276,15 +363,23 @@ public class PolarisCatalogsEventServiceDelegator implements PolarisCatalogsApiS
       String catalogRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeListGrantsForCatalogRole(
-        new CatalogsServiceEvents.BeforeListGrantsForCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_GRANTS_FOR_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     Response resp =
         delegate.listGrantsForCatalogRole(
             catalogName, catalogRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterListGrantsForCatalogRole(
-        new CatalogsServiceEvents.AfterListGrantsForCatalogRoleEvent(
-            eventMetadataFactory.create(), catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_GRANTS_FOR_CATALOG_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     return resp;
   }
 
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalRolesEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalRolesEventServiceDelegator.java
index d71163ec1..4d579b5f7 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalRolesEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalRolesEventServiceDelegator.java
@@ -31,8 +31,11 @@ import org.apache.polaris.core.admin.model.PrincipalRole;
 import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.admin.api.PolarisPrincipalRolesApiService;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
-import org.apache.polaris.service.events.PrincipalRolesServiceEvents;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 
 @Decorator
@@ -48,39 +51,53 @@ public class PolarisPrincipalRolesEventServiceDelegator implements PolarisPrinci
       CreatePrincipalRoleRequest request,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeCreatePrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeCreatePrincipalRoleEvent(
-            eventMetadataFactory.create(), request));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.CREATE_PRINCIPAL_ROLE_REQUEST, request)));
     Response resp = delegate.createPrincipalRole(request, realmContext, securityContext);
-    polarisEventListener.onAfterCreatePrincipalRole(
-        new PrincipalRolesServiceEvents.AfterCreatePrincipalRoleEvent(
-            eventMetadataFactory.create(), (PrincipalRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE, (PrincipalRole) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response deletePrincipalRole(
       String principalRoleName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeDeletePrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeDeletePrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DELETE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     Response resp = delegate.deletePrincipalRole(principalRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterDeletePrincipalRole(
-        new PrincipalRolesServiceEvents.AfterDeletePrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DELETE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     return resp;
   }
 
   @Override
   public Response getPrincipalRole(
       String principalRoleName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeGetPrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeGetPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     Response resp = delegate.getPrincipalRole(principalRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterGetPrincipalRole(
-        new PrincipalRolesServiceEvents.AfterGetPrincipalRoleEvent(
-            eventMetadataFactory.create(), (PrincipalRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE, (PrincipalRole) resp.getEntity())));
     return resp;
   }
 
@@ -90,27 +107,38 @@ public class PolarisPrincipalRolesEventServiceDelegator implements PolarisPrinci
       UpdatePrincipalRoleRequest updateRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeUpdatePrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeUpdatePrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName, updateRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.UPDATE_PRINCIPAL_ROLE_REQUEST, updateRequest)));
     Response resp =
         delegate.updatePrincipalRole(
             principalRoleName, updateRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdatePrincipalRole(
-        new PrincipalRolesServiceEvents.AfterUpdatePrincipalRoleEvent(
-            eventMetadataFactory.create(), (PrincipalRole) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE, (PrincipalRole) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response listPrincipalRoles(RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeListPrincipalRoles(
-        new PrincipalRolesServiceEvents.BeforeListPrincipalRolesEvent(
-            eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_PRINCIPAL_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     Response resp = delegate.listPrincipalRoles(realmContext, securityContext);
-    polarisEventListener.onAfterListPrincipalRoles(
-        new PrincipalRolesServiceEvents.AfterListPrincipalRolesEvent(
-            eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_PRINCIPAL_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     return resp;
   }
 
@@ -121,21 +149,25 @@ public class PolarisPrincipalRolesEventServiceDelegator implements PolarisPrinci
       GrantCatalogRoleRequest request,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeAssignCatalogRoleToPrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeAssignCatalogRoleToPrincipalRoleEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE,
             eventMetadataFactory.create(),
-            principalRoleName,
-            catalogName,
-            request.getCatalogRole().getName()));
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, request.getCatalogRole().getName())));
     Response resp =
         delegate.assignCatalogRoleToPrincipalRole(
             principalRoleName, catalogName, request, realmContext, securityContext);
-    polarisEventListener.onAfterAssignCatalogRoleToPrincipalRole(
-        new PrincipalRolesServiceEvents.AfterAssignCatalogRoleToPrincipalRoleEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE,
             eventMetadataFactory.create(),
-            principalRoleName,
-            catalogName,
-            request.getCatalogRole().getName()));
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, request.getCatalogRole().getName())));
     return resp;
   }
 
@@ -146,30 +178,44 @@ public class PolarisPrincipalRolesEventServiceDelegator implements PolarisPrinci
       String catalogRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeRevokeCatalogRoleFromPrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeRevokeCatalogRoleFromPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName, catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     Response resp =
         delegate.revokeCatalogRoleFromPrincipalRole(
             principalRoleName, catalogName, catalogRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterRevokeCatalogRoleFromPrincipalRole(
-        new PrincipalRolesServiceEvents.AfterRevokeCatalogRoleFromPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName, catalogName, catalogRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CATALOG_ROLE_NAME, catalogRoleName)));
     return resp;
   }
 
   @Override
   public Response listAssigneePrincipalsForPrincipalRole(
       String principalRoleName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeListAssigneePrincipalsForPrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeListAssigneePrincipalsForPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_ASSIGNEE_PRINCIPALS_FOR_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     Response resp =
         delegate.listAssigneePrincipalsForPrincipalRole(
             principalRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterListAssigneePrincipalsForPrincipalRole(
-        new PrincipalRolesServiceEvents.AfterListAssigneePrincipalsForPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_ASSIGNEE_PRINCIPALS_FOR_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     return resp;
   }
 
@@ -179,15 +225,23 @@ public class PolarisPrincipalRolesEventServiceDelegator implements PolarisPrinci
       String catalogName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeListCatalogRolesForPrincipalRole(
-        new PrincipalRolesServiceEvents.BeforeListCatalogRolesForPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName, catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)));
     Response resp =
         delegate.listCatalogRolesForPrincipalRole(
             principalRoleName, catalogName, realmContext, securityContext);
-    polarisEventListener.onAfterListCatalogRolesForPrincipalRole(
-        new PrincipalRolesServiceEvents.AfterListCatalogRolesForPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalRoleName, catalogName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)
+                .put(EventAttributes.CATALOG_NAME, catalogName)));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalsEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalsEventServiceDelegator.java
index bba04ee30..2b5e3decb 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalsEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisPrincipalsEventServiceDelegator.java
@@ -33,8 +33,11 @@ import org.apache.polaris.core.admin.model.ResetPrincipalRequest;
 import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.admin.api.PolarisPrincipalsApiService;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
-import org.apache.polaris.service.events.PrincipalsServiceEvents;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 
 @Decorator
@@ -48,14 +51,21 @@ public class PolarisPrincipalsEventServiceDelegator implements PolarisPrincipals
   @Override
   public Response createPrincipal(
       CreatePrincipalRequest request, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeCreatePrincipal(
-        new PrincipalsServiceEvents.BeforeCreatePrincipalEvent(
-            eventMetadataFactory.create(), request.getPrincipal().getName()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, request.getPrincipal().getName())));
     Response resp = delegate.createPrincipal(request, realmContext, securityContext);
-    polarisEventListener.onAfterCreatePrincipal(
-        new PrincipalsServiceEvents.AfterCreatePrincipalEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_PRINCIPAL,
             eventMetadataFactory.create(),
-            ((PrincipalWithCredentials) resp.getEntity()).getPrincipal()));
+            new AttributeMap()
+                .put(
+                    EventAttributes.PRINCIPAL,
+                    ((PrincipalWithCredentials) resp.getEntity()).getPrincipal())));
     return resp;
   }
 
@@ -65,42 +75,56 @@ public class PolarisPrincipalsEventServiceDelegator implements PolarisPrincipals
       ResetPrincipalRequest resetPrincipalRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeResetCredentials(
-        new PrincipalsServiceEvents.BeforeResetCredentialsEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_RESET_CREDENTIALS,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     Response resp =
         delegate.resetCredentials(
             principalName, resetPrincipalRequest, realmContext, securityContext);
-    polarisEventListener.onAfterResetCredentials(
-        new PrincipalsServiceEvents.AfterResetCredentialsEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_RESET_CREDENTIALS,
             eventMetadataFactory.create(),
-            ((PrincipalWithCredentials) resp.getEntity()).getPrincipal()));
+            new AttributeMap()
+                .put(
+                    EventAttributes.PRINCIPAL,
+                    ((PrincipalWithCredentials) resp.getEntity()).getPrincipal())));
     return resp;
   }
 
   @Override
   public Response deletePrincipal(
       String principalName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeDeletePrincipal(
-        new PrincipalsServiceEvents.BeforeDeletePrincipalEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DELETE_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     Response resp = delegate.deletePrincipal(principalName, realmContext, securityContext);
-    polarisEventListener.onAfterDeletePrincipal(
-        new PrincipalsServiceEvents.AfterDeletePrincipalEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DELETE_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     return resp;
   }
 
   @Override
   public Response getPrincipal(
       String principalName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeGetPrincipal(
-        new PrincipalsServiceEvents.BeforeGetPrincipalEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     Response resp = delegate.getPrincipal(principalName, realmContext, securityContext);
-    polarisEventListener.onAfterGetPrincipal(
-        new PrincipalsServiceEvents.AfterGetPrincipalEvent(
-            eventMetadataFactory.create(), (Principal) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL, (Principal) resp.getEntity())));
     return resp;
   }
 
@@ -110,38 +134,55 @@ public class PolarisPrincipalsEventServiceDelegator implements PolarisPrincipals
       UpdatePrincipalRequest updateRequest,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeUpdatePrincipal(
-        new PrincipalsServiceEvents.BeforeUpdatePrincipalEvent(
-            eventMetadataFactory.create(), principalName, updateRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, principalName)
+                .put(EventAttributes.UPDATE_PRINCIPAL_REQUEST, updateRequest)));
     Response resp =
         delegate.updatePrincipal(principalName, updateRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdatePrincipal(
-        new PrincipalsServiceEvents.AfterUpdatePrincipalEvent(
-            eventMetadataFactory.create(), (Principal) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_PRINCIPAL,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL, (Principal) resp.getEntity())));
     return resp;
   }
 
   @Override
   public Response rotateCredentials(
       String principalName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeRotateCredentials(
-        new PrincipalsServiceEvents.BeforeRotateCredentialsEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ROTATE_CREDENTIALS,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     Response resp = delegate.rotateCredentials(principalName, realmContext, securityContext);
     PrincipalWithCredentials principalWithCredentials = (PrincipalWithCredentials) resp.getEntity();
-    polarisEventListener.onAfterRotateCredentials(
-        new PrincipalsServiceEvents.AfterRotateCredentialsEvent(
-            eventMetadataFactory.create(), principalWithCredentials.getPrincipal()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_ROTATE_CREDENTIALS,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL, principalWithCredentials.getPrincipal())));
     return resp;
   }
 
   @Override
   public Response listPrincipals(RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeListPrincipals(
-        new PrincipalsServiceEvents.BeforeListPrincipalsEvent(eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_PRINCIPALS,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     Response resp = delegate.listPrincipals(realmContext, securityContext);
-    polarisEventListener.onAfterListPrincipals(
-        new PrincipalsServiceEvents.AfterListPrincipalsEvent(eventMetadataFactory.create()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_PRINCIPALS,
+            eventMetadataFactory.create(),
+            new AttributeMap()));
     return resp;
   }
 
@@ -151,14 +192,22 @@ public class PolarisPrincipalsEventServiceDelegator implements PolarisPrincipals
       GrantPrincipalRoleRequest request,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeAssignPrincipalRole(
-        new PrincipalsServiceEvents.BeforeAssignPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalName, request.getPrincipalRole()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ASSIGN_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, principalName)
+                .put(EventAttributes.PRINCIPAL_ROLE, request.getPrincipalRole())));
     Response resp =
         delegate.assignPrincipalRole(principalName, request, realmContext, securityContext);
-    polarisEventListener.onAfterAssignPrincipalRole(
-        new PrincipalsServiceEvents.AfterAssignPrincipalRoleEvent(
-            eventMetadataFactory.create(), principalName, request.getPrincipalRole()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_ASSIGN_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, principalName)
+                .put(EventAttributes.PRINCIPAL_ROLE, request.getPrincipalRole())));
     return resp;
   }
 
@@ -168,29 +217,41 @@ public class PolarisPrincipalsEventServiceDelegator implements PolarisPrincipals
       String principalRoleName,
       RealmContext realmContext,
       SecurityContext securityContext) {
-    polarisEventListener.onBeforeRevokePrincipalRole(
-        new PrincipalsServiceEvents.BeforeRevokePrincipalRoleEvent(
-            eventMetadataFactory.create(), principalName, principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REVOKE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, principalName)
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     Response resp =
         delegate.revokePrincipalRole(
             principalName, principalRoleName, realmContext, securityContext);
-    polarisEventListener.onAfterRevokePrincipalRole(
-        new PrincipalsServiceEvents.AfterRevokePrincipalRoleEvent(
-            eventMetadataFactory.create(), principalName, principalRoleName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REVOKE_PRINCIPAL_ROLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.PRINCIPAL_NAME, principalName)
+                .put(EventAttributes.PRINCIPAL_ROLE_NAME, principalRoleName)));
     return resp;
   }
 
   @Override
   public Response listPrincipalRolesAssigned(
       String principalName, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeListAssignedPrincipalRoles(
-        new PrincipalsServiceEvents.BeforeListAssignedPrincipalRolesEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_ASSIGNED_PRINCIPAL_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     Response resp =
         delegate.listPrincipalRolesAssigned(principalName, realmContext, securityContext);
-    polarisEventListener.onAfterListAssignedPrincipalRoles(
-        new PrincipalsServiceEvents.AfterListAssignedPrincipalRolesEvent(
-            eventMetadataFactory.create(), principalName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_ASSIGNED_PRINCIPAL_ROLES,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.PRINCIPAL_NAME, principalName)));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/CatalogGenericTableEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/CatalogGenericTableEventServiceDelegator.java
index 3bd827a5f..fd0680a7a 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/CatalogGenericTableEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/CatalogGenericTableEventServiceDelegator.java
@@ -29,8 +29,11 @@ import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.catalog.CatalogPrefixParser;
 import org.apache.polaris.service.catalog.api.PolarisCatalogGenericTableApiService;
 import org.apache.polaris.service.catalog.common.CatalogAdapter;
-import org.apache.polaris.service.events.CatalogGenericTableServiceEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.types.CreateGenericTableRequest;
 import org.apache.polaris.service.types.LoadGenericTableResponse;
@@ -53,18 +56,27 @@ public class CatalogGenericTableEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeCreateGenericTable(
-        new CatalogGenericTableServiceEvents.BeforeCreateGenericTableEvent(
-            eventMetadataFactory.create(), catalogName, namespace, createGenericTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_GENERIC_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.CREATE_GENERIC_TABLE_REQUEST, createGenericTableRequest)));
     Response resp =
         delegate.createGenericTable(
             prefix, namespace, createGenericTableRequest, realmContext, securityContext);
-    polarisEventListener.onAfterCreateGenericTable(
-        new CatalogGenericTableServiceEvents.AfterCreateGenericTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_GENERIC_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            ((LoadGenericTableResponse) resp.getEntity()).getTable()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(
+                    EventAttributes.GENERIC_TABLE,
+                    ((LoadGenericTableResponse) resp.getEntity()).getTable())));
     return resp;
   }
 
@@ -76,14 +88,24 @@ public class CatalogGenericTableEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeDropGenericTable(
-        new CatalogGenericTableServiceEvents.BeforeDropGenericTableEvent(
-            eventMetadataFactory.create(), catalogName, namespace, genericTable));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DROP_GENERIC_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.GENERIC_TABLE_NAME, genericTable)));
     Response resp =
         delegate.dropGenericTable(prefix, namespace, genericTable, realmContext, securityContext);
-    polarisEventListener.onAfterDropGenericTable(
-        new CatalogGenericTableServiceEvents.AfterDropGenericTableEvent(
-            eventMetadataFactory.create(), catalogName, namespace, genericTable));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DROP_GENERIC_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.GENERIC_TABLE_NAME, genericTable)));
     return resp;
   }
 
@@ -96,15 +118,23 @@ public class CatalogGenericTableEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeListGenericTables(
-        new CatalogGenericTableServiceEvents.BeforeListGenericTablesEvent(
-            eventMetadataFactory.create(), catalogName, namespace));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_GENERIC_TABLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)));
     Response resp =
         delegate.listGenericTables(
             prefix, namespace, pageToken, pageSize, realmContext, securityContext);
-    polarisEventListener.onAfterListGenericTables(
-        new CatalogGenericTableServiceEvents.AfterListGenericTablesEvent(
-            eventMetadataFactory.create(), catalogName, namespace));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_GENERIC_TABLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)));
     return resp;
   }
 
@@ -116,17 +146,26 @@ public class CatalogGenericTableEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeLoadGenericTable(
-        new CatalogGenericTableServiceEvents.BeforeLoadGenericTableEvent(
-            eventMetadataFactory.create(), catalogName, namespace, genericTable));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_GENERIC_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.GENERIC_TABLE_NAME, genericTable)));
     Response resp =
         delegate.loadGenericTable(prefix, namespace, genericTable, realmContext, securityContext);
-    polarisEventListener.onAfterLoadGenericTable(
-        new CatalogGenericTableServiceEvents.AfterLoadGenericTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_GENERIC_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            ((LoadGenericTableResponse) resp.getEntity()).getTable()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(
+                    EventAttributes.GENERIC_TABLE,
+                    ((LoadGenericTableResponse) resp.getEntity()).getTable())));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
index bda38f009..80cabab93 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
@@ -130,8 +130,11 @@ import org.apache.polaris.service.catalog.io.FileIOFactory;
 import org.apache.polaris.service.catalog.io.FileIOUtil;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
 import org.apache.polaris.service.catalog.validation.IcebergPropertiesValidation;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.task.TaskExecutor;
 import org.apache.polaris.service.types.NotificationRequest;
@@ -1380,9 +1383,13 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       if (latestLocation == null) {
         disableRefresh();
       } else {
-        polarisEventListener.onBeforeRefreshTable(
-            new IcebergRestCatalogEvents.BeforeRefreshTableEvent(
-                eventMetadataFactory.create(), catalogName, tableIdentifier));
+        polarisEventListener.onEvent(
+            new PolarisEvent(
+                PolarisEventType.BEFORE_REFRESH_TABLE,
+                eventMetadataFactory.create(),
+                new AttributeMap()
+                    .put(EventAttributes.CATALOG_NAME, catalogName)
+                    .put(EventAttributes.TABLE_IDENTIFIER, tableIdentifier)));
         refreshFromMetadataLocation(
             latestLocation,
             SHOULD_RETRY_REFRESH_PREDICATE,
@@ -1402,9 +1409,13 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
                       Set.of(PolarisStorageActions.READ, PolarisStorageActions.LIST));
               return TableMetadataParser.read(fileIO, metadataLocation);
             });
-        polarisEventListener.onAfterRefreshTable(
-            new IcebergRestCatalogEvents.AfterRefreshTableEvent(
-                eventMetadataFactory.create(), catalogName, tableIdentifier));
+        polarisEventListener.onEvent(
+            new PolarisEvent(
+                PolarisEventType.AFTER_REFRESH_TABLE,
+                eventMetadataFactory.create(),
+                new AttributeMap()
+                    .put(EventAttributes.CATALOG_NAME, catalogName)
+                    .put(EventAttributes.TABLE_IDENTIFIER, tableIdentifier)));
       }
     }
 
@@ -1782,9 +1793,13 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       if (latestLocation == null) {
         disableRefresh();
       } else {
-        polarisEventListener.onBeforeRefreshView(
-            new IcebergRestCatalogEvents.BeforeRefreshViewEvent(
-                eventMetadataFactory.create(), catalogName, identifier));
+        polarisEventListener.onEvent(
+            new PolarisEvent(
+                PolarisEventType.BEFORE_REFRESH_VIEW,
+                eventMetadataFactory.create(),
+                new AttributeMap()
+                    .put(EventAttributes.CATALOG_NAME, catalogName)
+                    .put(EventAttributes.VIEW_IDENTIFIER, identifier)));
         refreshFromMetadataLocation(
             latestLocation,
             SHOULD_RETRY_REFRESH_PREDICATE,
@@ -1806,16 +1821,26 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
               return ViewMetadataParser.read(fileIO.newInputFile(metadataLocation));
             });
-        polarisEventListener.onAfterRefreshView(
-            new IcebergRestCatalogEvents.AfterRefreshViewEvent(
-                eventMetadataFactory.create(), catalogName, identifier));
+        polarisEventListener.onEvent(
+            new PolarisEvent(
+                PolarisEventType.AFTER_REFRESH_VIEW,
+                eventMetadataFactory.create(),
+                new AttributeMap()
+                    .put(EventAttributes.CATALOG_NAME, catalogName)
+                    .put(EventAttributes.VIEW_IDENTIFIER, identifier)));
       }
     }
 
     public void doCommit(ViewMetadata base, ViewMetadata metadata) {
-      polarisEventListener.onBeforeCommitView(
-          new IcebergRestCatalogEvents.BeforeCommitViewEvent(
-              eventMetadataFactory.create(), catalogName, identifier, base, metadata));
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.BEFORE_COMMIT_VIEW,
+              eventMetadataFactory.create(),
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, catalogName)
+                  .put(EventAttributes.VIEW_IDENTIFIER, identifier)
+                  .put(EventAttributes.VIEW_METADATA_BEFORE, base)
+                  .put(EventAttributes.VIEW_METADATA_AFTER, metadata)));
 
       // TODO: Maybe avoid writing metadata if there's definitely a transaction conflict
       LOGGER.debug(
@@ -1915,9 +1940,15 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
         updateTableLike(identifier, entity);
       }
 
-      polarisEventListener.onAfterCommitView(
-          new IcebergRestCatalogEvents.AfterCommitViewEvent(
-              eventMetadataFactory.create(), catalogName, identifier, base, metadata));
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_COMMIT_VIEW,
+              eventMetadataFactory.create(),
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, catalogName)
+                  .put(EventAttributes.VIEW_IDENTIFIER, identifier)
+                  .put(EventAttributes.VIEW_METADATA_BEFORE, base)
+                  .put(EventAttributes.VIEW_METADATA_AFTER, metadata)));
     }
 
     protected String writeNewMetadataIfRequired(ViewMetadata metadata) {
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
index 29a298a28..3a8a35e9e 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
@@ -45,54 +45,11 @@ import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.catalog.CatalogPrefixParser;
 import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService;
 import org.apache.polaris.service.catalog.common.CatalogAdapter;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCheckExistsNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCheckExistsTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCheckExistsViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCreateNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCreateTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterCreateViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterDropNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterDropTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterDropViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterListNamespacesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterListTablesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterListViewsEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterLoadCredentialsEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterLoadNamespaceMetadataEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterLoadTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterLoadViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterRegisterTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterRenameTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterRenameViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterReplaceViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterSendNotificationEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterUpdateNamespacePropertiesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.AfterUpdateTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCheckExistsNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCheckExistsTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCheckExistsViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCreateNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCreateTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeCreateViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeDropNamespaceEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeDropTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeDropViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeListNamespacesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeListTablesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeListViewsEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeLoadCredentialsEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeLoadNamespaceMetadataEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeLoadTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeLoadViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeRegisterTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeRenameTableEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeRenameViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeReplaceViewEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeSendNotificationEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeUpdateNamespacePropertiesEvent;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents.BeforeUpdateTableEvent;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.types.CommitTableRequest;
 import org.apache.polaris.service.types.CommitViewRequest;
@@ -131,18 +88,24 @@ public class IcebergRestCatalogEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeCreateNamespace(
-        new BeforeCreateNamespaceEvent(
-            eventMetadataFactory.create(), catalogName, createNamespaceRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_NAMESPACE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.CREATE_NAMESPACE_REQUEST, createNamespaceRequest)));
     Response resp =
         delegate.createNamespace(prefix, createNamespaceRequest, realmContext, securityContext);
     CreateNamespaceResponse createNamespaceResponse = (CreateNamespaceResponse) resp.getEntity();
-    polarisEventListener.onAfterCreateNamespace(
-        new AfterCreateNamespaceEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_NAMESPACE,
             eventMetadataFactory.create(),
-            catalogName,
-            createNamespaceResponse.namespace(),
-            createNamespaceResponse.properties()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, createNamespaceResponse.namespace())
+                .put(EventAttributes.NAMESPACE_PROPERTIES, createNamespaceResponse.properties())));
     return resp;
   }
 
@@ -155,12 +118,22 @@ public class IcebergRestCatalogEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeListNamespaces(
-        new BeforeListNamespacesEvent(eventMetadataFactory.create(), catalogName, parent));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_NAMESPACES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.PARENT_NAMESPACE_FQN, parent)));
     Response resp =
         delegate.listNamespaces(prefix, pageToken, pageSize, parent, realmContext, securityContext);
-    polarisEventListener.onAfterListNamespaces(
-        new AfterListNamespacesEvent(eventMetadataFactory.create(), catalogName, parent));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_NAMESPACES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.PARENT_NAMESPACE_FQN, parent)));
     return resp;
   }
 
@@ -168,18 +141,24 @@ public class IcebergRestCatalogEventServiceDelegator
   public Response loadNamespaceMetadata(
       String prefix, String namespace, RealmContext realmContext, SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeLoadNamespaceMetadata(
-        new BeforeLoadNamespaceMetadataEvent(
-            eventMetadataFactory.create(), catalogName, decodeNamespace(namespace)));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_NAMESPACE_METADATA,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, decodeNamespace(namespace))));
     Response resp =
         delegate.loadNamespaceMetadata(prefix, namespace, realmContext, securityContext);
     GetNamespaceResponse getNamespaceResponse = (GetNamespaceResponse) resp.getEntity();
-    polarisEventListener.onAfterLoadNamespaceMetadata(
-        new AfterLoadNamespaceMetadataEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_NAMESPACE_METADATA,
             eventMetadataFactory.create(),
-            catalogName,
-            getNamespaceResponse.namespace(),
-            getNamespaceResponse.properties()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, getNamespaceResponse.namespace())
+                .put(EventAttributes.NAMESPACE_PROPERTIES, getNamespaceResponse.properties())));
     return resp;
   }
 
@@ -188,13 +167,21 @@ public class IcebergRestCatalogEventServiceDelegator
       String prefix, String namespace, RealmContext realmContext, SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeCheckExistsNamespace(
-        new BeforeCheckExistsNamespaceEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CHECK_EXISTS_NAMESPACE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     Response resp = delegate.namespaceExists(prefix, namespace, realmContext, securityContext);
-    polarisEventListener.onAfterCheckExistsNamespace(
-        new AfterCheckExistsNamespaceEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CHECK_EXISTS_NAMESPACE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     return resp;
   }
 
@@ -202,12 +189,21 @@ public class IcebergRestCatalogEventServiceDelegator
   public Response dropNamespace(
       String prefix, String namespace, RealmContext realmContext, SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeDropNamespace(
-        new BeforeDropNamespaceEvent(
-            eventMetadataFactory.create(), catalogName, decodeNamespace(namespace)));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DROP_NAMESPACE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, decodeNamespace(namespace))));
     Response resp = delegate.dropNamespace(prefix, namespace, realmContext, securityContext);
-    polarisEventListener.onAfterDropNamespace(
-        new AfterDropNamespaceEvent(eventMetadataFactory.create(), catalogName, namespace));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DROP_NAMESPACE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_FQN, namespace)));
     return resp;
   }
 
@@ -220,21 +216,29 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeUpdateNamespaceProperties(
-        new BeforeUpdateNamespacePropertiesEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_NAMESPACE_PROPERTIES,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            updateNamespacePropertiesRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(
+                    EventAttributes.UPDATE_NAMESPACE_PROPERTIES_REQUEST,
+                    updateNamespacePropertiesRequest)));
     Response resp =
         delegate.updateProperties(
             prefix, namespace, updateNamespacePropertiesRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdateNamespaceProperties(
-        new AfterUpdateNamespacePropertiesEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_NAMESPACE_PROPERTIES,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            (UpdateNamespacePropertiesResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(
+                    EventAttributes.UPDATE_NAMESPACE_PROPERTIES_RESPONSE,
+                    (UpdateNamespacePropertiesResponse) resp.getEntity())));
     return resp;
   }
 
@@ -248,13 +252,15 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeCreateTable(
-        new BeforeCreateTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            createTableRequest,
-            accessDelegationMode));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.CREATE_TABLE_REQUEST, createTableRequest)
+                .put(EventAttributes.ACCESS_DELEGATION_MODE, accessDelegationMode)));
     Response resp =
         delegate.createTable(
             prefix,
@@ -264,13 +270,15 @@ public class IcebergRestCatalogEventServiceDelegator
             realmContext,
             securityContext);
     if (!createTableRequest.stageCreate()) {
-      polarisEventListener.onAfterCreateTable(
-          new AfterCreateTableEvent(
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_CREATE_TABLE,
               eventMetadataFactory.create(),
-              catalogName,
-              namespaceObj,
-              createTableRequest.name(),
-              (LoadTableResponse) resp.getEntity()));
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, catalogName)
+                  .put(EventAttributes.NAMESPACE, namespaceObj)
+                  .put(EventAttributes.TABLE_NAME, createTableRequest.name())
+                  .put(EventAttributes.LOAD_TABLE_RESPONSE, (LoadTableResponse) resp.getEntity())));
     }
     return resp;
   }
@@ -285,12 +293,22 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeListTables(
-        new BeforeListTablesEvent(eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_TABLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     Response resp =
         delegate.listTables(prefix, namespace, pageToken, pageSize, realmContext, securityContext);
-    polarisEventListener.onAfterListTables(
-        new AfterListTablesEvent(eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_TABLES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     return resp;
   }
 
@@ -306,15 +324,17 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeLoadTable(
-        new BeforeLoadTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            table,
-            accessDelegationMode,
-            ifNoneMatchString,
-            snapshots));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.ACCESS_DELEGATION_MODE, accessDelegationMode)
+                .put(EventAttributes.IF_NONE_MATCH_STRING, ifNoneMatchString)
+                .put(EventAttributes.SNAPSHOTS, snapshots)));
     Response resp =
         delegate.loadTable(
             prefix,
@@ -325,13 +345,15 @@ public class IcebergRestCatalogEventServiceDelegator
             snapshots,
             realmContext,
             securityContext);
-    polarisEventListener.onAfterLoadTable(
-        new AfterLoadTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            table,
-            (LoadTableResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.LOAD_TABLE_RESPONSE, (LoadTableResponse) resp.getEntity())));
     return resp;
   }
 
@@ -344,13 +366,23 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeCheckExistsTable(
-        new BeforeCheckExistsTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CHECK_EXISTS_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)));
     Response resp = delegate.tableExists(prefix, namespace, table, realmContext, securityContext);
-    polarisEventListener.onAfterCheckExistsTable(
-        new AfterCheckExistsTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CHECK_EXISTS_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)));
     return resp;
   }
 
@@ -364,14 +396,26 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeDropTable(
-        new BeforeDropTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table, purgeRequested));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DROP_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.PURGE_REQUESTED, purgeRequested)));
     Response resp =
         delegate.dropTable(prefix, namespace, table, purgeRequested, realmContext, securityContext);
-    polarisEventListener.onAfterDropTable(
-        new AfterDropTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table, purgeRequested));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DROP_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.PURGE_REQUESTED, purgeRequested)));
     return resp;
   }
 
@@ -384,19 +428,26 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeRegisterTable(
-        new BeforeRegisterTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, registerTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REGISTER_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.REGISTER_TABLE_REQUEST, registerTableRequest)));
     Response resp =
         delegate.registerTable(
             prefix, namespace, registerTableRequest, realmContext, securityContext);
-    polarisEventListener.onAfterRegisterTable(
-        new AfterRegisterTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REGISTER_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            registerTableRequest.name(),
-            (LoadTableResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, registerTableRequest.name())
+                .put(EventAttributes.LOAD_TABLE_RESPONSE, (LoadTableResponse) resp.getEntity())));
     return resp;
   }
 
@@ -407,11 +458,21 @@ public class IcebergRestCatalogEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeRenameTable(
-        new BeforeRenameTableEvent(eventMetadataFactory.create(), catalogName, renameTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_RENAME_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.RENAME_TABLE_REQUEST, renameTableRequest)));
     Response resp = delegate.renameTable(prefix, renameTableRequest, realmContext, securityContext);
-    polarisEventListener.onAfterRenameTable(
-        new AfterRenameTableEvent(eventMetadataFactory.create(), catalogName, renameTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_RENAME_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.RENAME_TABLE_REQUEST, renameTableRequest)));
     return resp;
   }
 
@@ -425,20 +486,28 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeUpdateTable(
-        new BeforeUpdateTableEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table, commitTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_TABLE,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.UPDATE_TABLE_REQUEST, commitTableRequest)));
     Response resp =
         delegate.updateTable(
             prefix, namespace, table, commitTableRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdateTable(
-        new AfterUpdateTableEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_TABLE,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            table,
-            commitTableRequest,
-            (LoadTableResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.UPDATE_TABLE_REQUEST, commitTableRequest)
+                .put(EventAttributes.LOAD_TABLE_RESPONSE, (LoadTableResponse) resp.getEntity())));
     return resp;
   }
 
@@ -451,18 +520,25 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeCreateView(
-        new BeforeCreateViewEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, createViewRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.CREATE_VIEW_REQUEST, createViewRequest)));
     Response resp =
         delegate.createView(prefix, namespace, createViewRequest, realmContext, securityContext);
-    polarisEventListener.onAfterCreateView(
-        new AfterCreateViewEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_VIEW,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            createViewRequest.name(),
-            (LoadViewResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, createViewRequest.name())
+                .put(EventAttributes.LOAD_VIEW_RESPONSE, (LoadViewResponse) resp.getEntity())));
     return resp;
   }
 
@@ -476,12 +552,22 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeListViews(
-        new BeforeListViewsEvent(eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_VIEWS,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     Response resp =
         delegate.listViews(prefix, namespace, pageToken, pageSize, realmContext, securityContext);
-    polarisEventListener.onAfterListViews(
-        new AfterListViewsEvent(eventMetadataFactory.create(), catalogName, namespaceObj));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_VIEWS,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)));
     return resp;
   }
 
@@ -494,14 +580,24 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeLoadCredentials(
-        new BeforeLoadCredentialsEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_CREDENTIALS,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)));
     Response resp =
         delegate.loadCredentials(prefix, namespace, table, realmContext, securityContext);
-    polarisEventListener.onAfterLoadCredentials(
-        new AfterLoadCredentialsEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_CREDENTIALS,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)));
     return resp;
   }
 
@@ -514,16 +610,24 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeLoadView(
-        new BeforeLoadViewEvent(eventMetadataFactory.create(), catalogName, namespaceObj, view));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)));
     Response resp = delegate.loadView(prefix, namespace, view, realmContext, securityContext);
-    polarisEventListener.onAfterLoadView(
-        new AfterLoadViewEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_VIEW,
             eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            view,
-            (LoadViewResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)
+                .put(EventAttributes.LOAD_VIEW_RESPONSE, (LoadViewResponse) resp.getEntity())));
     return resp;
   }
 
@@ -536,13 +640,23 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeCheckExistsView(
-        new BeforeCheckExistsViewEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, view));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CHECK_EXISTS_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)));
     Response resp = delegate.viewExists(prefix, namespace, view, realmContext, securityContext);
-    polarisEventListener.onAfterCheckExistsView(
-        new AfterCheckExistsViewEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, view));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CHECK_EXISTS_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)));
     return resp;
   }
 
@@ -555,11 +669,23 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeDropView(
-        new BeforeDropViewEvent(eventMetadataFactory.create(), catalogName, namespaceObj, view));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DROP_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)));
     Response resp = delegate.dropView(prefix, namespace, view, realmContext, securityContext);
-    polarisEventListener.onAfterDropView(
-        new AfterDropViewEvent(eventMetadataFactory.create(), catalogName, namespaceObj, view));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DROP_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)));
     return resp;
   }
 
@@ -570,11 +696,21 @@ public class IcebergRestCatalogEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeRenameView(
-        new BeforeRenameViewEvent(eventMetadataFactory.create(), catalogName, renameTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_RENAME_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.RENAME_TABLE_REQUEST, renameTableRequest)));
     Response resp = delegate.renameView(prefix, renameTableRequest, realmContext, securityContext);
-    polarisEventListener.onAfterRenameView(
-        new AfterRenameViewEvent(eventMetadataFactory.create(), catalogName, renameTableRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_RENAME_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.RENAME_TABLE_REQUEST, renameTableRequest)));
     return resp;
   }
 
@@ -588,20 +724,28 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeReplaceView(
-        new BeforeReplaceViewEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, view, commitViewRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REPLACE_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)
+                .put(EventAttributes.COMMIT_VIEW_REQUEST, commitViewRequest)));
     Response resp =
         delegate.replaceView(
             prefix, namespace, view, commitViewRequest, realmContext, securityContext);
-    polarisEventListener.onAfterReplaceView(
-        new AfterReplaceViewEvent(
-            eventMetadataFactory.create(),
-            catalogName,
-            namespaceObj,
-            view,
-            commitViewRequest,
-            (LoadViewResponse) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REPLACE_VIEW,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, view)
+                .put(EventAttributes.COMMIT_VIEW_REQUEST, commitViewRequest)
+                .put(EventAttributes.LOAD_VIEW_RESPONSE, (LoadViewResponse) resp.getEntity())));
     return resp;
   }
 
@@ -612,37 +756,47 @@ public class IcebergRestCatalogEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeCommitTransaction(
-        new IcebergRestCatalogEvents.BeforeCommitTransactionEvent(
-            eventMetadataFactory.create(), catalogName, commitTransactionRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_COMMIT_TRANSACTION,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.COMMIT_TRANSACTION_REQUEST, commitTransactionRequest)));
     for (UpdateTableRequest req : commitTransactionRequest.tableChanges()) {
-      polarisEventListener.onBeforeUpdateTable(
-          new BeforeUpdateTableEvent(
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.BEFORE_UPDATE_TABLE,
               eventMetadataFactory.create(),
-              catalogName,
-              req.identifier().namespace(),
-              req.identifier().name(),
-              req));
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, catalogName)
+                  .put(EventAttributes.NAMESPACE, req.identifier().namespace())
+                  .put(EventAttributes.TABLE_NAME, req.identifier().name())
+                  .put(EventAttributes.UPDATE_TABLE_REQUEST, req)));
     }
     Response resp =
         delegate.commitTransaction(prefix, commitTransactionRequest, realmContext, securityContext);
-    polarisEventListener.onAfterCommitTransaction(
-        new IcebergRestCatalogEvents.AfterCommitTransactionEvent(
-            eventMetadataFactory.create(), catalogName, commitTransactionRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_COMMIT_TRANSACTION,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.COMMIT_TRANSACTION_REQUEST, commitTransactionRequest)));
     for (UpdateTableRequest req : commitTransactionRequest.tableChanges()) {
-      polarisEventListener.onAfterUpdateTable(
-          new AfterUpdateTableEvent(
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_UPDATE_TABLE,
               eventMetadataFactory.create(),
-              catalogName,
-              req.identifier().namespace(),
-              req.identifier().name(),
-              req,
-              null));
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, catalogName)
+                  .put(EventAttributes.NAMESPACE, req.identifier().namespace())
+                  .put(EventAttributes.TABLE_NAME, req.identifier().name())
+                  .put(EventAttributes.UPDATE_TABLE_REQUEST, req)));
     }
     return resp;
   }
 
-  /** This API is currently a no-op in Polaris. */
   @Override
   public Response reportMetrics(
       String prefix,
@@ -665,15 +819,26 @@ public class IcebergRestCatalogEventServiceDelegator
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
     Namespace namespaceObj = decodeNamespace(namespace);
-    polarisEventListener.onBeforeSendNotification(
-        new BeforeSendNotificationEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table, notificationRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_SEND_NOTIFICATION,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)
+                .put(EventAttributes.NOTIFICATION_REQUEST, notificationRequest)));
     Response resp =
         delegate.sendNotification(
             prefix, namespace, table, notificationRequest, realmContext, securityContext);
-    polarisEventListener.onAfterSendNotification(
-        new AfterSendNotificationEvent(
-            eventMetadataFactory.create(), catalogName, namespaceObj, table));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_SEND_NOTIFICATION,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.TABLE_NAME, table)));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestConfigurationEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestConfigurationEventServiceDelegator.java
index 9342bc844..697d7d80f 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestConfigurationEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestConfigurationEventServiceDelegator.java
@@ -29,8 +29,11 @@ import jakarta.ws.rs.core.SecurityContext;
 import org.apache.iceberg.rest.responses.ConfigResponse;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.catalog.api.IcebergRestConfigurationApiService;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 
 @Decorator
@@ -52,19 +55,23 @@ public class IcebergRestConfigurationEventServiceDelegator
     this.eventMetadataFactory = eventMetadataFactory;
   }
 
-  // Default constructor for CDI
   public IcebergRestConfigurationEventServiceDelegator() {}
 
   @Override
   public Response getConfig(
       String warehouse, RealmContext realmContext, SecurityContext securityContext) {
-    polarisEventListener.onBeforeGetConfig(
-        new IcebergRestCatalogEvents.BeforeGetConfigEvent(
-            eventMetadataFactory.create(), warehouse));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_CONFIG,
+            eventMetadataFactory.create(),
+            new AttributeMap().put(EventAttributes.WAREHOUSE, warehouse)));
     Response resp = delegate.getConfig(warehouse, realmContext, securityContext);
-    polarisEventListener.onAfterGetConfig(
-        new IcebergRestCatalogEvents.AfterGetConfigEvent(
-            eventMetadataFactory.create(), (ConfigResponse) resp.getEntity()));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_CONFIG,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CONFIG_RESPONSE, (ConfigResponse) resp.getEntity())));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/CatalogPolicyEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/CatalogPolicyEventServiceDelegator.java
index 8c2a0e556..b415726dd 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/CatalogPolicyEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/CatalogPolicyEventServiceDelegator.java
@@ -29,8 +29,11 @@ import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.service.catalog.CatalogPrefixParser;
 import org.apache.polaris.service.catalog.api.PolarisCatalogPolicyApiService;
 import org.apache.polaris.service.catalog.common.CatalogAdapter;
-import org.apache.polaris.service.events.CatalogPolicyServiceEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.types.AttachPolicyRequest;
 import org.apache.polaris.service.types.CreatePolicyRequest;
@@ -57,18 +60,25 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeCreatePolicy(
-        new CatalogPolicyServiceEvents.BeforeCreatePolicyEvent(
-            eventMetadataFactory.create(), catalogName, namespace, createPolicyRequest));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_POLICY,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.CREATE_POLICY_REQUEST, createPolicyRequest)));
     Response resp =
         delegate.createPolicy(
             prefix, namespace, createPolicyRequest, realmContext, securityContext);
-    polarisEventListener.onAfterCreatePolicy(
-        new CatalogPolicyServiceEvents.AfterCreatePolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_CREATE_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            (LoadPolicyResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.LOAD_POLICY_RESPONSE, (LoadPolicyResponse) resp.getEntity())));
     return resp;
   }
 
@@ -82,15 +92,25 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeListPolicies(
-        new CatalogPolicyServiceEvents.BeforeListPoliciesEvent(
-            eventMetadataFactory.create(), catalogName, namespace, policyType));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LIST_POLICIES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_TYPE, policyType)));
     Response resp =
         delegate.listPolicies(
             prefix, namespace, pageToken, pageSize, policyType, realmContext, securityContext);
-    polarisEventListener.onAfterListPolicies(
-        new CatalogPolicyServiceEvents.AfterListPoliciesEvent(
-            eventMetadataFactory.create(), catalogName, namespace, policyType));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LIST_POLICIES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_TYPE, policyType)));
     return resp;
   }
 
@@ -102,17 +122,24 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeLoadPolicy(
-        new CatalogPolicyServiceEvents.BeforeLoadPolicyEvent(
-            eventMetadataFactory.create(), catalogName, namespace, policyName));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_LOAD_POLICY,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)));
     Response resp =
         delegate.loadPolicy(prefix, namespace, policyName, realmContext, securityContext);
-    polarisEventListener.onAfterLoadPolicy(
-        new CatalogPolicyServiceEvents.AfterLoadPolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_LOAD_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            (LoadPolicyResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.LOAD_POLICY_RESPONSE, (LoadPolicyResponse) resp.getEntity())));
     return resp;
   }
 
@@ -125,22 +152,26 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeUpdatePolicy(
-        new CatalogPolicyServiceEvents.BeforeUpdatePolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_UPDATE_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            policyName,
-            updatePolicyRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.UPDATE_POLICY_REQUEST, updatePolicyRequest)));
     Response resp =
         delegate.updatePolicy(
             prefix, namespace, policyName, updatePolicyRequest, realmContext, securityContext);
-    polarisEventListener.onAfterUpdatePolicy(
-        new CatalogPolicyServiceEvents.AfterUpdatePolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_UPDATE_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            (LoadPolicyResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.LOAD_POLICY_RESPONSE, (LoadPolicyResponse) resp.getEntity())));
     return resp;
   }
 
@@ -153,15 +184,27 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeDropPolicy(
-        new CatalogPolicyServiceEvents.BeforeDropPolicyEvent(
-            eventMetadataFactory.create(), catalogName, namespace, policyName, detachAll));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DROP_POLICY,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.DETACH_ALL, detachAll)));
     Response resp =
         delegate.dropPolicy(
             prefix, namespace, policyName, detachAll, realmContext, securityContext);
-    polarisEventListener.onAfterDropPolicy(
-        new CatalogPolicyServiceEvents.AfterDropPolicyEvent(
-            eventMetadataFactory.create(), catalogName, namespace, policyName, detachAll));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DROP_POLICY,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.DETACH_ALL, detachAll)));
     return resp;
   }
 
@@ -174,23 +217,27 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeAttachPolicy(
-        new CatalogPolicyServiceEvents.BeforeAttachPolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ATTACH_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            policyName,
-            attachPolicyRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.ATTACH_POLICY_REQUEST, attachPolicyRequest)));
     Response resp =
         delegate.attachPolicy(
             prefix, namespace, policyName, attachPolicyRequest, realmContext, securityContext);
-    polarisEventListener.onAfterAttachPolicy(
-        new CatalogPolicyServiceEvents.AfterAttachPolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_ATTACH_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            policyName,
-            attachPolicyRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.ATTACH_POLICY_REQUEST, attachPolicyRequest)));
     return resp;
   }
 
@@ -203,23 +250,27 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeDetachPolicy(
-        new CatalogPolicyServiceEvents.BeforeDetachPolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_DETACH_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            policyName,
-            detachPolicyRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.DETACH_POLICY_REQUEST, detachPolicyRequest)));
     Response resp =
         delegate.detachPolicy(
             prefix, namespace, policyName, detachPolicyRequest, realmContext, securityContext);
-    polarisEventListener.onAfterDetachPolicy(
-        new CatalogPolicyServiceEvents.AfterDetachPolicyEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_DETACH_POLICY,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            policyName,
-            detachPolicyRequest));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.POLICY_NAME, policyName)
+                .put(EventAttributes.DETACH_POLICY_REQUEST, detachPolicyRequest)));
     return resp;
   }
 
@@ -234,9 +285,15 @@ public class CatalogPolicyEventServiceDelegator
       RealmContext realmContext,
       SecurityContext securityContext) {
     String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix);
-    polarisEventListener.onBeforeGetApplicablePolicies(
-        new CatalogPolicyServiceEvents.BeforeGetApplicablePoliciesEvent(
-            eventMetadataFactory.create(), catalogName, namespace, targetName, policyType));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_GET_APPLICABLE_POLICIES,
+            eventMetadataFactory.create(),
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.TARGET_NAME, targetName)
+                .put(EventAttributes.POLICY_TYPE, policyType)));
     Response resp =
         delegate.getApplicablePolicies(
             prefix,
@@ -247,14 +304,18 @@ public class CatalogPolicyEventServiceDelegator
             policyType,
             realmContext,
             securityContext);
-    polarisEventListener.onAfterGetApplicablePolicies(
-        new CatalogPolicyServiceEvents.AfterGetApplicablePoliciesEvent(
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_GET_APPLICABLE_POLICIES,
             eventMetadataFactory.create(),
-            catalogName,
-            namespace,
-            targetName,
-            policyType,
-            (GetApplicablePoliciesResponse) resp.getEntity()));
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE_NAME, namespace)
+                .put(EventAttributes.TARGET_NAME, targetName)
+                .put(EventAttributes.POLICY_TYPE, policyType)
+                .put(
+                    EventAttributes.GET_APPLICABLE_POLICIES_RESPONSE,
+                    (GetApplicablePoliciesResponse) resp.getEntity())));
     return resp;
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/AfterAttemptTaskEvent.java b/runtime/service/src/main/java/org/apache/polaris/service/events/AfterAttemptTaskEvent.java
deleted file mode 100644
index e06dcfa53..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/AfterAttemptTaskEvent.java
+++ /dev/null
@@ -1,36 +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.polaris.service.events;
-
-/**
- * Emitted after an attempt of an async task, such as manifest file cleanup, finishes.
- *
- * @param taskEntityId The ID of the TaskEntity.
- * @param attempt The attempt number. Each retry of the task will have its own attempt number. The
- *     initial (non-retried) attempt starts counting from 1.
- * @param success Whether the attempt succeeded.
- */
-public record AfterAttemptTaskEvent(
-    PolarisEventMetadata metadata, long taskEntityId, int attempt, boolean success)
-    implements PolarisEvent {
-  @Override
-  public PolarisEventType type() {
-    return PolarisEventType.AFTER_ATTEMPT_TASK;
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java b/runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java
new file mode 100644
index 000000000..0d8ea6b93
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java
@@ -0,0 +1,123 @@
+/*
+ * 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.polaris.service.events;
+
+import com.google.common.reflect.TypeToken;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.RESTMessage;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/** Whitelist of types allowed for event attributes. */
+final class AllowedAttributeTypes {
+  private AllowedAttributeTypes() {}
+
+  static final Set<Class<?>> ALLOWED_TYPES =
+      Set.of(
+          // Primitives
+          String.class,
+          Boolean.class,
+          Number.class,
+          // Iceberg types
+          RESTMessage.class,
+          Namespace.class,
+          TableIdentifier.class,
+          TableMetadata.class,
+          ViewMetadata.class,
+          // Polaris admin model types
+          Catalog.class,
+          Principal.class,
+          PrincipalRole.class,
+          CatalogRole.class,
+          GrantResource.class,
+          UpdatePrincipalRequest.class,
+          CreatePrincipalRoleRequest.class,
+          UpdatePrincipalRoleRequest.class,
+          UpdateCatalogRequest.class,
+          UpdateCatalogRoleRequest.class,
+          AddGrantRequest.class,
+          RevokeGrantRequest.class,
+          PolarisPrivilege.class,
+          // Polaris service types
+          CommitViewRequest.class,
+          GenericTable.class,
+          CreateGenericTableRequest.class,
+          CreatePolicyRequest.class,
+          UpdatePolicyRequest.class,
+          LoadPolicyResponse.class,
+          AttachPolicyRequest.class,
+          DetachPolicyRequest.class,
+          GetApplicablePoliciesResponse.class,
+          NotificationRequest.class);
+
+  private static final Set<Class<?>> COLLECTION_TYPES = Set.of(List.class, Set.class, Map.class);
+
+  static boolean isAllowed(TypeToken<?> type) {
+    Class<?> rawType = type.getRawType();
+    if (COLLECTION_TYPES.contains(rawType)) {
+      for (var typeParam : rawType.getTypeParameters()) {
+        TypeToken<?> resolvedType = type.resolveType(typeParam);
+        if (!isSubtypeOfAllowedType(resolvedType.getRawType())) {
+          return false;
+        }
+      }
+      return true;
+    }
+    return isSubtypeOfAllowedType(rawType);
+  }
+
+  private static final ClassValue<Boolean> ALLOWED_CACHE =
+      new ClassValue<>() {
+        @Override
+        protected Boolean computeValue(Class<?> type) {
+          return ALLOWED_TYPES.stream().anyMatch(t -> t.isAssignableFrom(type));
+        }
+      };
+
+  private static boolean isSubtypeOfAllowedType(Class<?> rawType) {
+    return ALLOWED_CACHE.get(rawType);
+  }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeKey.java b/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeKey.java
new file mode 100644
index 000000000..eb47cd1d0
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeKey.java
@@ -0,0 +1,57 @@
+/*
+ * 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.polaris.service.events;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.reflect.TypeToken;
+import java.util.Objects;
+
+/**
+ * A type-safe key for event attributes. This allows for strongly-typed attribute access while
+ * maintaining flexibility for custom attributes.
+ *
+ * <p>Attribute types are validated at key creation time to ensure they can be serialized. See
+ * {@link AllowedAttributeTypes} for the list of allowed types.
+ *
+ * @param <T> the type of the attribute value
+ */
+public record AttributeKey<T>(@JsonValue String name, TypeToken<T> type) {
+
+  public AttributeKey {
+    Objects.requireNonNull(name, "name");
+    Objects.requireNonNull(type, "type");
+    if (!AllowedAttributeTypes.isAllowed(type)) {
+      throw new IllegalArgumentException("Type " + type + " is not allowed for event attributes");
+    }
+  }
+
+  public AttributeKey(String name, Class<T> type) {
+    this(name, TypeToken.of(type));
+  }
+
+  @SuppressWarnings("unchecked")
+  public T cast(Object value) {
+    return (T) type.getRawType().cast(value);
+  }
+
+  @Override
+  public String toString() {
+    return "AttributeKey{" + name + ", " + type.getRawType().getSimpleName() + "}";
+  }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeMap.java b/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeMap.java
new file mode 100644
index 000000000..e2abf718c
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/AttributeMap.java
@@ -0,0 +1,66 @@
+/*
+ * 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.polaris.service.events;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/** A type-safe container for event attributes. This class is mutable and not thread-safe! */
+public final class AttributeMap {
+  private final Map<AttributeKey<?>, Object> attributes;
+
+  public AttributeMap() {
+    this.attributes = new HashMap<>();
+  }
+
+  public AttributeMap(AttributeMap other) {
+    this.attributes = new HashMap<>(other.attributes);
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T> Optional<T> get(AttributeKey<T> key) {
+    return Optional.ofNullable((T) attributes.get(key));
+  }
+
+  public <T> T getRequired(AttributeKey<T> key) {
+    return get(key)
+        .orElseThrow(
+            () -> new IllegalStateException("Required attribute " + key.name() + " not found"));
+  }
+
+  public <T> AttributeMap put(AttributeKey<T> key, T value) {
+    if (value != null) {
+      attributes.put(key, value);
+    }
+    return this;
+  }
+
+  public boolean contains(AttributeKey<?> key) {
+    return attributes.containsKey(key);
+  }
+
+  public int size() {
+    return attributes.size();
+  }
+
+  public boolean isEmpty() {
+    return attributes.isEmpty();
+  }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeAttemptTaskEvent.java b/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeAttemptTaskEvent.java
deleted file mode 100644
index c0bf5b355..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeAttemptTaskEvent.java
+++ /dev/null
@@ -1,34 +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.polaris.service.events;
-
-/**
- * Emitted before an attempt of an async task, such as manifest file cleanup, begins.
- *
- * @param taskEntityId The ID of the TaskEntity
- * @param attempt The attempt number. Each retry of the task will have its own attempt number. The
- *     initial (non-retried) attempt starts counting from 1.
- */
-public record BeforeAttemptTaskEvent(PolarisEventMetadata metadata, long taskEntityId, int attempt)
-    implements PolarisEvent {
-  @Override
-  public PolarisEventType type() {
-    return PolarisEventType.BEFORE_ATTEMPT_TASK;
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeLimitRequestRateEvent.java b/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeLimitRequestRateEvent.java
deleted file mode 100644
index f98395107..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/BeforeLimitRequestRateEvent.java
+++ /dev/null
@@ -1,33 +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.polaris.service.events;
-
-/**
- * Emitted before the RateLimiterFilter rejects a request due to exceeding the rate limit.
- *
- * @param method The request's HTTP method
- * @param absolutePath The request's absolute path
- */
-public record BeforeLimitRequestRateEvent(
-    PolarisEventMetadata metadata, String method, String absolutePath) implements PolarisEvent {
-  @Override
-  public PolarisEventType type() {
-    return PolarisEventType.BEFORE_LIMIT_REQUEST_RATE;
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogGenericTableServiceEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogGenericTableServiceEvents.java
deleted file mode 100644
index f939dee0f..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogGenericTableServiceEvents.java
+++ /dev/null
@@ -1,98 +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.polaris.service.events;
-
-import org.apache.polaris.service.types.CreateGenericTableRequest;
-import org.apache.polaris.service.types.GenericTable;
-
-public class CatalogGenericTableServiceEvents {
-  public record BeforeCreateGenericTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      CreateGenericTableRequest request)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_GENERIC_TABLE;
-    }
-  }
-
-  public record AfterCreateGenericTableEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, GenericTable table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_GENERIC_TABLE;
-    }
-  }
-
-  public record BeforeDropGenericTableEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String tableName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DROP_GENERIC_TABLE;
-    }
-  }
-
-  public record AfterDropGenericTableEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String tableName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DROP_GENERIC_TABLE;
-    }
-  }
-
-  public record BeforeListGenericTablesEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_GENERIC_TABLES;
-    }
-  }
-
-  public record AfterListGenericTablesEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_GENERIC_TABLES;
-    }
-  }
-
-  public record BeforeLoadGenericTableEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String tableName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_GENERIC_TABLE;
-    }
-  }
-
-  public record AfterLoadGenericTableEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, GenericTable table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_GENERIC_TABLE;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogPolicyServiceEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogPolicyServiceEvents.java
deleted file mode 100644
index 45e578d83..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogPolicyServiceEvents.java
+++ /dev/null
@@ -1,230 +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.polaris.service.events;
-
-import org.apache.polaris.service.types.AttachPolicyRequest;
-import org.apache.polaris.service.types.CreatePolicyRequest;
-import org.apache.polaris.service.types.DetachPolicyRequest;
-import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
-import org.apache.polaris.service.types.LoadPolicyResponse;
-import org.apache.polaris.service.types.UpdatePolicyRequest;
-
-/**
- * Event records for Catalog Policy operations. Each operation has corresponding "Before" and
- * "After" event records.
- */
-public class CatalogPolicyServiceEvents {
-
-  // Policy CRUD Events
-  public record BeforeCreatePolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      CreatePolicyRequest createPolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_POLICY;
-    }
-  }
-
-  public record AfterCreatePolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      LoadPolicyResponse loadPolicyResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_POLICY;
-    }
-  }
-
-  public record BeforeListPoliciesEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String policyType)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_POLICIES;
-    }
-  }
-
-  public record AfterListPoliciesEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String policyType)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_POLICIES;
-    }
-  }
-
-  public record BeforeLoadPolicyEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace, String policyName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_POLICY;
-    }
-  }
-
-  public record AfterLoadPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      LoadPolicyResponse loadPolicyResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_POLICY;
-    }
-  }
-
-  public record BeforeUpdatePolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      UpdatePolicyRequest updatePolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_POLICY;
-    }
-  }
-
-  public record AfterUpdatePolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      LoadPolicyResponse loadPolicyResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_POLICY;
-    }
-  }
-
-  public record BeforeDropPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      Boolean detachAll)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DROP_POLICY;
-    }
-  }
-
-  public record AfterDropPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      Boolean detachAll)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DROP_POLICY;
-    }
-  }
-
-  // Policy Attachment Events
-  public record BeforeAttachPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      AttachPolicyRequest attachPolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_ATTACH_POLICY;
-    }
-  }
-
-  public record AfterAttachPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      AttachPolicyRequest attachPolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_ATTACH_POLICY;
-    }
-  }
-
-  public record BeforeDetachPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      DetachPolicyRequest detachPolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DETACH_POLICY;
-    }
-  }
-
-  public record AfterDetachPolicyEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String policyName,
-      DetachPolicyRequest detachPolicyRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DETACH_POLICY;
-    }
-  }
-
-  // Policy Query Events
-  public record BeforeGetApplicablePoliciesEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String targetName,
-      String policyType)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_APPLICABLE_POLICIES;
-    }
-  }
-
-  public record AfterGetApplicablePoliciesEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String namespace,
-      String targetName,
-      String policyType,
-      GetApplicablePoliciesResponse getApplicablePoliciesResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_APPLICABLE_POLICIES;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogsServiceEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogsServiceEvents.java
deleted file mode 100644
index f2a54edfc..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/CatalogsServiceEvents.java
+++ /dev/null
@@ -1,322 +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.polaris.service.events;
-
-import org.apache.polaris.core.admin.model.AddGrantRequest;
-import org.apache.polaris.core.admin.model.Catalog;
-import org.apache.polaris.core.admin.model.CatalogRole;
-import org.apache.polaris.core.admin.model.GrantResource;
-import org.apache.polaris.core.admin.model.RevokeGrantRequest;
-import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
-import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
-import org.apache.polaris.core.entity.PolarisPrivilege;
-
-public class CatalogsServiceEvents {
-
-  public record BeforeCreateCatalogEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_CATALOG;
-    }
-  }
-
-  public record AfterCreateCatalogEvent(PolarisEventMetadata metadata, Catalog catalog)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_CATALOG;
-    }
-  }
-
-  public record BeforeDeleteCatalogEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DELETE_CATALOG;
-    }
-  }
-
-  public record AfterDeleteCatalogEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DELETE_CATALOG;
-    }
-  }
-
-  public record BeforeGetCatalogEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_CATALOG;
-    }
-  }
-
-  public record AfterGetCatalogEvent(PolarisEventMetadata metadata, Catalog catalog)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_CATALOG;
-    }
-  }
-
-  public record BeforeUpdateCatalogEvent(
-      PolarisEventMetadata metadata, String catalogName, UpdateCatalogRequest updateRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_CATALOG;
-    }
-  }
-
-  public record AfterUpdateCatalogEvent(PolarisEventMetadata metadata, Catalog catalog)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_CATALOG;
-    }
-  }
-
-  public record BeforeListCatalogsEvent(PolarisEventMetadata metadata) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_CATALOGS;
-    }
-  }
-
-  public record AfterListCatalogsEvent(PolarisEventMetadata metadata) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_CATALOGS;
-    }
-  }
-
-  public record BeforeCreateCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterCreateCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, CatalogRole catalogRole)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeDeleteCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DELETE_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterDeleteCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DELETE_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeGetCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterGetCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, CatalogRole catalogRole)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeUpdateCatalogRoleEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String catalogRoleName,
-      UpdateCatalogRoleRequest updateRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterUpdateCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, CatalogRole updatedCatalogRole)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeListCatalogRolesEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_CATALOG_ROLES;
-    }
-  }
-
-  public record AfterListCatalogRolesEvent(PolarisEventMetadata metadata, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_CATALOG_ROLES;
-    }
-  }
-
-  /**
-   * Event fired before a grant is added to a catalog role in Polaris.
-   *
-   * @param catalogName the name of the catalog
-   * @param catalogRoleName the name of the catalog role
-   * @param grantRequest the grant request
-   */
-  public record BeforeAddGrantToCatalogRoleEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String catalogRoleName,
-      AddGrantRequest grantRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_ADD_GRANT_TO_CATALOG_ROLE;
-    }
-  }
-
-  /**
-   * Event fired after a grant is added to a catalog role in Polaris.
-   *
-   * @param catalogName the name of the catalog
-   * @param catalogRoleName the name of the catalog role
-   * @param privilege the privilege granted
-   * @param grantResource the grant resource
-   */
-  public record AfterAddGrantToCatalogRoleEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String catalogRoleName,
-      PolarisPrivilege privilege,
-      GrantResource grantResource)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_ADD_GRANT_TO_CATALOG_ROLE;
-    }
-  }
-
-  /**
-   * Event fired before a grant is revoked from a catalog role in Polaris.
-   *
-   * @param catalogName the name of the catalog
-   * @param catalogRoleName the name of the catalog role
-   * @param grantRequest the revoke grant request
-   * @param cascade whether the revoke is cascading
-   */
-  public record BeforeRevokeGrantFromCatalogRoleEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String catalogRoleName,
-      RevokeGrantRequest grantRequest,
-      Boolean cascade)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REVOKE_GRANT_FROM_CATALOG_ROLE;
-    }
-  }
-
-  /**
-   * Event fired after a grant is revoked from a catalog role in Polaris.
-   *
-   * @param catalogName the name of the catalog
-   * @param catalogRoleName the name of the catalog role
-   * @param privilege the privilege revoked
-   * @param grantResource the revoke grant resource
-   * @param cascade whether to cascade the revocation
-   */
-  public record AfterRevokeGrantFromCatalogRoleEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      String catalogRoleName,
-      PolarisPrivilege privilege,
-      GrantResource grantResource,
-      Boolean cascade)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REVOKE_GRANT_FROM_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeListAssigneePrincipalRolesForCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_ASSIGNEE_PRINCIPAL_ROLES_FOR_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterListAssigneePrincipalRolesForCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_ASSIGNEE_PRINCIPAL_ROLES_FOR_CATALOG_ROLE;
-    }
-  }
-
-  public record BeforeListGrantsForCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_GRANTS_FOR_CATALOG_ROLE;
-    }
-  }
-
-  public record AfterListGrantsForCatalogRoleEvent(
-      PolarisEventMetadata metadata, String catalogName, String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_GRANTS_FOR_CATALOG_ROLE;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java b/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java
new file mode 100644
index 000000000..7c62f84c3
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java
@@ -0,0 +1,233 @@
+/*
+ * 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.polaris.service.events;
+
+import com.google.common.reflect.TypeToken;
+import java.util.Map;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/**
+ * Standard attribute keys for Polaris events. These keys provide type-safe access to common event
+ * attributes and enable automatic pruning/filtering logic.
+ */
+public final class EventAttributes {
+  private EventAttributes() {}
+
+  // Catalog attributes
+  public static final AttributeKey<String> CATALOG_NAME =
+      new AttributeKey<>("catalog_name", String.class);
+  public static final AttributeKey<Catalog> CATALOG = new AttributeKey<>("catalog", Catalog.class);
+  public static final AttributeKey<UpdateCatalogRequest> UPDATE_CATALOG_REQUEST =
+      new AttributeKey<>("update_catalog_request", UpdateCatalogRequest.class);
+
+  // Namespace attributes
+  public static final AttributeKey<Namespace> NAMESPACE =
+      new AttributeKey<>("namespace", Namespace.class);
+  public static final AttributeKey<String> NAMESPACE_FQN =
+      new AttributeKey<>("namespace_fqn", String.class);
+  public static final AttributeKey<String> PARENT_NAMESPACE_FQN =
+      new AttributeKey<>("parent_namespace_fqn", String.class);
+  public static final AttributeKey<CreateNamespaceRequest> CREATE_NAMESPACE_REQUEST =
+      new AttributeKey<>("create_namespace_request", CreateNamespaceRequest.class);
+  public static final AttributeKey<UpdateNamespacePropertiesRequest>
+      UPDATE_NAMESPACE_PROPERTIES_REQUEST =
+          new AttributeKey<>(
+              "update_namespace_properties_request", UpdateNamespacePropertiesRequest.class);
+  public static final AttributeKey<UpdateNamespacePropertiesResponse>
+      UPDATE_NAMESPACE_PROPERTIES_RESPONSE =
+          new AttributeKey<>(
+              "update_namespace_properties_response", UpdateNamespacePropertiesResponse.class);
+
+  public static final AttributeKey<Map<String, String>> NAMESPACE_PROPERTIES =
+      new AttributeKey<>("namespace_properties", new TypeToken<>() {});
+
+  // Table attributes
+  public static final AttributeKey<String> TABLE_NAME =
+      new AttributeKey<>("table_name", String.class);
+  public static final AttributeKey<TableIdentifier> TABLE_IDENTIFIER =
+      new AttributeKey<>("table_identifier", TableIdentifier.class);
+  public static final AttributeKey<CreateTableRequest> CREATE_TABLE_REQUEST =
+      new AttributeKey<>("create_table_request", CreateTableRequest.class);
+  public static final AttributeKey<UpdateTableRequest> UPDATE_TABLE_REQUEST =
+      new AttributeKey<>("update_table_request", UpdateTableRequest.class);
+  public static final AttributeKey<RegisterTableRequest> REGISTER_TABLE_REQUEST =
+      new AttributeKey<>("register_table_request", RegisterTableRequest.class);
+  public static final AttributeKey<RenameTableRequest> RENAME_TABLE_REQUEST =
+      new AttributeKey<>("rename_table_request", RenameTableRequest.class);
+  public static final AttributeKey<LoadTableResponse> LOAD_TABLE_RESPONSE =
+      new AttributeKey<>("load_table_response", LoadTableResponse.class);
+  public static final AttributeKey<String> ACCESS_DELEGATION_MODE =
+      new AttributeKey<>("access_delegation_mode", String.class);
+  public static final AttributeKey<String> IF_NONE_MATCH_STRING =
+      new AttributeKey<>("if_none_match_string", String.class);
+  public static final AttributeKey<String> SNAPSHOTS =
+      new AttributeKey<>("snapshots", String.class);
+  public static final AttributeKey<Boolean> PURGE_REQUESTED =
+      new AttributeKey<>("purge_requested", Boolean.class);
+
+  // View attributes
+  public static final AttributeKey<String> VIEW_NAME =
+      new AttributeKey<>("view_name", String.class);
+  public static final AttributeKey<TableIdentifier> VIEW_IDENTIFIER =
+      new AttributeKey<>("view_identifier", TableIdentifier.class);
+  public static final AttributeKey<CreateViewRequest> CREATE_VIEW_REQUEST =
+      new AttributeKey<>("create_view_request", CreateViewRequest.class);
+  public static final AttributeKey<CommitViewRequest> COMMIT_VIEW_REQUEST =
+      new AttributeKey<>("commit_view_request", CommitViewRequest.class);
+  public static final AttributeKey<ViewMetadata> VIEW_METADATA_BEFORE =
+      new AttributeKey<>("view_metadata_before", ViewMetadata.class);
+  public static final AttributeKey<ViewMetadata> VIEW_METADATA_AFTER =
+      new AttributeKey<>("view_metadata_after", ViewMetadata.class);
+  public static final AttributeKey<LoadViewResponse> LOAD_VIEW_RESPONSE =
+      new AttributeKey<>("load_view_response", LoadViewResponse.class);
+
+  // Principal attributes
+  public static final AttributeKey<String> PRINCIPAL_NAME =
+      new AttributeKey<>("principal_name", String.class);
+  public static final AttributeKey<Principal> PRINCIPAL =
+      new AttributeKey<>("principal", Principal.class);
+  public static final AttributeKey<UpdatePrincipalRequest> UPDATE_PRINCIPAL_REQUEST =
+      new AttributeKey<>("update_principal_request", UpdatePrincipalRequest.class);
+
+  // Principal Role attributes
+  public static final AttributeKey<String> PRINCIPAL_ROLE_NAME =
+      new AttributeKey<>("principal_role_name", String.class);
+  public static final AttributeKey<PrincipalRole> PRINCIPAL_ROLE =
+      new AttributeKey<>("principal_role", PrincipalRole.class);
+  public static final AttributeKey<CreatePrincipalRoleRequest> CREATE_PRINCIPAL_ROLE_REQUEST =
+      new AttributeKey<>("create_principal_role_request", CreatePrincipalRoleRequest.class);
+  public static final AttributeKey<UpdatePrincipalRoleRequest> UPDATE_PRINCIPAL_ROLE_REQUEST =
+      new AttributeKey<>("update_principal_role_request", UpdatePrincipalRoleRequest.class);
+
+  // Catalog Role attributes
+  public static final AttributeKey<String> CATALOG_ROLE_NAME =
+      new AttributeKey<>("catalog_role_name", String.class);
+  public static final AttributeKey<CatalogRole> CATALOG_ROLE =
+      new AttributeKey<>("catalog_role", CatalogRole.class);
+  public static final AttributeKey<UpdateCatalogRoleRequest> UPDATE_CATALOG_ROLE_REQUEST =
+      new AttributeKey<>("update_catalog_role_request", UpdateCatalogRoleRequest.class);
+
+  // Grant attributes
+  public static final AttributeKey<AddGrantRequest> ADD_GRANT_REQUEST =
+      new AttributeKey<>("add_grant_request", AddGrantRequest.class);
+  public static final AttributeKey<RevokeGrantRequest> REVOKE_GRANT_REQUEST =
+      new AttributeKey<>("revoke_grant_request", RevokeGrantRequest.class);
+  public static final AttributeKey<GrantResource> GRANT_RESOURCE =
+      new AttributeKey<>("grant_resource", GrantResource.class);
+  public static final AttributeKey<PolarisPrivilege> PRIVILEGE =
+      new AttributeKey<>("privilege", PolarisPrivilege.class);
+  public static final AttributeKey<Boolean> CASCADE = new AttributeKey<>("cascade", Boolean.class);
+
+  // Transaction attributes
+  public static final AttributeKey<CommitTransactionRequest> COMMIT_TRANSACTION_REQUEST =
+      new AttributeKey<>("commit_transaction_request", CommitTransactionRequest.class);
+
+  // Notification attributes
+  public static final AttributeKey<NotificationRequest> NOTIFICATION_REQUEST =
+      new AttributeKey<>("notification_request", NotificationRequest.class);
+
+  // Configuration attributes
+  public static final AttributeKey<String> WAREHOUSE =
+      new AttributeKey<>("warehouse", String.class);
+  public static final AttributeKey<ConfigResponse> CONFIG_RESPONSE =
+      new AttributeKey<>("config_response", ConfigResponse.class);
+
+  // Task attributes
+  public static final AttributeKey<Long> TASK_ENTITY_ID =
+      new AttributeKey<>("task_entity_id", Long.class);
+  public static final AttributeKey<Integer> TASK_ATTEMPT =
+      new AttributeKey<>("task_attempt", Integer.class);
+  public static final AttributeKey<Boolean> TASK_SUCCESS =
+      new AttributeKey<>("task_success", Boolean.class);
+
+  // Rate limiting attributes
+  public static final AttributeKey<String> HTTP_METHOD =
+      new AttributeKey<>("http_method", String.class);
+  public static final AttributeKey<String> REQUEST_URI =
+      new AttributeKey<>("request_uri", String.class);
+
+  // Generic table attributes
+  public static final AttributeKey<String> NAMESPACE_NAME =
+      new AttributeKey<>("namespace_name", String.class);
+  public static final AttributeKey<String> GENERIC_TABLE_NAME =
+      new AttributeKey<>("generic_table_name", String.class);
+  public static final AttributeKey<GenericTable> GENERIC_TABLE =
+      new AttributeKey<>("generic_table", GenericTable.class);
+  public static final AttributeKey<CreateGenericTableRequest> CREATE_GENERIC_TABLE_REQUEST =
+      new AttributeKey<>("create_generic_table_request", CreateGenericTableRequest.class);
+
+  // Policy attributes
+  public static final AttributeKey<String> POLICY_NAME =
+      new AttributeKey<>("policy_name", String.class);
+  public static final AttributeKey<String> POLICY_TYPE =
+      new AttributeKey<>("policy_type", String.class);
+  public static final AttributeKey<String> TARGET_NAME =
+      new AttributeKey<>("target_name", String.class);
+  public static final AttributeKey<Boolean> DETACH_ALL =
+      new AttributeKey<>("detach_all", Boolean.class);
+  public static final AttributeKey<CreatePolicyRequest> CREATE_POLICY_REQUEST =
+      new AttributeKey<>("create_policy_request", CreatePolicyRequest.class);
+  public static final AttributeKey<UpdatePolicyRequest> UPDATE_POLICY_REQUEST =
+      new AttributeKey<>("update_policy_request", UpdatePolicyRequest.class);
+  public static final AttributeKey<LoadPolicyResponse> LOAD_POLICY_RESPONSE =
+      new AttributeKey<>("load_policy_response", LoadPolicyResponse.class);
+  public static final AttributeKey<AttachPolicyRequest> ATTACH_POLICY_REQUEST =
+      new AttributeKey<>("attach_policy_request", AttachPolicyRequest.class);
+  public static final AttributeKey<DetachPolicyRequest> DETACH_POLICY_REQUEST =
+      new AttributeKey<>("detach_policy_request", DetachPolicyRequest.class);
+  public static final AttributeKey<GetApplicablePoliciesResponse> GET_APPLICABLE_POLICIES_RESPONSE =
+      new AttributeKey<>("get_applicable_policies_response", GetApplicablePoliciesResponse.class);
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/IcebergRestCatalogEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/IcebergRestCatalogEvents.java
deleted file mode 100644
index 610d716cf..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/IcebergRestCatalogEvents.java
+++ /dev/null
@@ -1,648 +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.polaris.service.events;
-
-import java.util.Map;
-import org.apache.iceberg.catalog.Namespace;
-import org.apache.iceberg.catalog.TableIdentifier;
-import org.apache.iceberg.rest.requests.CommitTransactionRequest;
-import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
-import org.apache.iceberg.rest.requests.CreateTableRequest;
-import org.apache.iceberg.rest.requests.CreateViewRequest;
-import org.apache.iceberg.rest.requests.RegisterTableRequest;
-import org.apache.iceberg.rest.requests.RenameTableRequest;
-import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
-import org.apache.iceberg.rest.requests.UpdateTableRequest;
-import org.apache.iceberg.rest.responses.ConfigResponse;
-import org.apache.iceberg.rest.responses.LoadTableResponse;
-import org.apache.iceberg.rest.responses.LoadViewResponse;
-import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
-import org.apache.iceberg.view.ViewMetadata;
-import org.apache.polaris.service.types.CommitViewRequest;
-import org.apache.polaris.service.types.NotificationRequest;
-
-/**
- * Event records for Iceberg REST Catalog operations. Each operation has corresponding "Before" and
- * "After" event records.
- */
-public class IcebergRestCatalogEvents {
-
-  // Namespace Events
-  public record BeforeCreateNamespaceEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      CreateNamespaceRequest createNamespaceRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_NAMESPACE;
-    }
-  }
-
-  public record AfterCreateNamespaceEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      Map<String, String> namespaceProperties)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_NAMESPACE;
-    }
-  }
-
-  public record BeforeListNamespacesEvent(
-      PolarisEventMetadata metadata, String catalogName, String parent) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_NAMESPACES;
-    }
-  }
-
-  public record AfterListNamespacesEvent(
-      PolarisEventMetadata metadata, String catalogName, String parent) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_NAMESPACES;
-    }
-  }
-
-  public record BeforeLoadNamespaceMetadataEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_NAMESPACE_METADATA;
-    }
-  }
-
-  public record AfterLoadNamespaceMetadataEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      Map<String, String> namespaceProperties)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_NAMESPACE_METADATA;
-    }
-  }
-
-  public record BeforeCheckExistsNamespaceEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CHECK_EXISTS_NAMESPACE;
-    }
-  }
-
-  public record AfterCheckExistsNamespaceEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CHECK_EXISTS_NAMESPACE;
-    }
-  }
-
-  public record BeforeDropNamespaceEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DROP_NAMESPACE;
-    }
-  }
-
-  public record AfterDropNamespaceEvent(
-      PolarisEventMetadata metadata, String catalogName, String namespace) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DROP_NAMESPACE;
-    }
-  }
-
-  public record BeforeUpdateNamespacePropertiesEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      UpdateNamespacePropertiesRequest updateNamespacePropertiesRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_NAMESPACE_PROPERTIES;
-    }
-  }
-
-  public record AfterUpdateNamespacePropertiesEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      UpdateNamespacePropertiesResponse updateNamespacePropertiesResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_NAMESPACE_PROPERTIES;
-    }
-  }
-
-  // Table Events
-  public record BeforeCreateTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      CreateTableRequest createTableRequest,
-      String accessDelegationMode)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_TABLE;
-    }
-  }
-
-  public record AfterCreateTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String tableName,
-      LoadTableResponse loadTableResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_TABLE;
-    }
-  }
-
-  public record BeforeListTablesEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_TABLES;
-    }
-  }
-
-  public record AfterListTablesEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_TABLES;
-    }
-  }
-
-  public record BeforeLoadTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String table,
-      String accessDelegationMode,
-      String ifNoneMatchString,
-      String snapshots)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_TABLE;
-    }
-  }
-
-  public record AfterLoadTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String tableName,
-      LoadTableResponse loadTableResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_TABLE;
-    }
-  }
-
-  public record BeforeCheckExistsTableEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CHECK_EXISTS_TABLE;
-    }
-  }
-
-  public record AfterCheckExistsTableEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CHECK_EXISTS_TABLE;
-    }
-  }
-
-  public record BeforeDropTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String table,
-      Boolean purgeRequested)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DROP_TABLE;
-    }
-  }
-
-  public record AfterDropTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String table,
-      Boolean purgeRequested)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DROP_TABLE;
-    }
-  }
-
-  public record BeforeRegisterTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      RegisterTableRequest registerTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REGISTER_TABLE;
-    }
-  }
-
-  public record AfterRegisterTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String tableName,
-      LoadTableResponse loadTableResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REGISTER_TABLE;
-    }
-  }
-
-  public record BeforeRenameTableEvent(
-      PolarisEventMetadata metadata, String catalogName, RenameTableRequest renameTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_RENAME_TABLE;
-    }
-  }
-
-  public record AfterRenameTableEvent(
-      PolarisEventMetadata metadata, String catalogName, RenameTableRequest renameTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_RENAME_TABLE;
-    }
-  }
-
-  public record BeforeUpdateTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String sourceTable,
-      UpdateTableRequest commitTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_TABLE;
-    }
-  }
-
-  /** LoadTableResponse is optional; it will not be populated in case of a transaction. */
-  public record AfterUpdateTableEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String sourceTable,
-      UpdateTableRequest commitTableRequest,
-      LoadTableResponse loadTableResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_TABLE;
-    }
-  }
-
-  // View Events
-  public record BeforeCreateViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      CreateViewRequest createViewRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_VIEW;
-    }
-  }
-
-  public record AfterCreateViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String viewName,
-      LoadViewResponse loadViewResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_VIEW;
-    }
-  }
-
-  public record BeforeListViewsEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_VIEWS;
-    }
-  }
-
-  public record AfterListViewsEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_VIEWS;
-    }
-  }
-
-  public record BeforeLoadViewEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String view)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_VIEW;
-    }
-  }
-
-  public record AfterLoadViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String viewName,
-      LoadViewResponse loadViewResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_VIEW;
-    }
-  }
-
-  public record BeforeCheckExistsViewEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String view)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CHECK_EXISTS_VIEW;
-    }
-  }
-
-  public record AfterCheckExistsViewEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String view)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CHECK_EXISTS_VIEW;
-    }
-  }
-
-  public record BeforeDropViewEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String view)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DROP_VIEW;
-    }
-  }
-
-  public record AfterDropViewEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String view)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DROP_VIEW;
-    }
-  }
-
-  public record BeforeRenameViewEvent(
-      PolarisEventMetadata metadata, String catalogName, RenameTableRequest renameTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_RENAME_VIEW;
-    }
-  }
-
-  public record AfterRenameViewEvent(
-      PolarisEventMetadata metadata, String catalogName, RenameTableRequest renameTableRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_RENAME_VIEW;
-    }
-  }
-
-  public record BeforeReplaceViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String sourceView,
-      CommitViewRequest commitViewRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REPLACE_VIEW;
-    }
-  }
-
-  public record AfterReplaceViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String sourceView,
-      CommitViewRequest commitViewRequest,
-      LoadViewResponse loadViewResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REPLACE_VIEW;
-    }
-  }
-
-  // Credential Events
-  public record BeforeLoadCredentialsEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LOAD_CREDENTIALS;
-    }
-  }
-
-  public record AfterLoadCredentialsEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LOAD_CREDENTIALS;
-    }
-  }
-
-  // Transaction Events
-  public record BeforeCommitTransactionEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      CommitTransactionRequest commitTransactionRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_COMMIT_TRANSACTION;
-    }
-  }
-
-  // TODO: Add all PolarisEntities that were modified with this transaction.
-  public record AfterCommitTransactionEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      CommitTransactionRequest commitTransactionRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_COMMIT_TRANSACTION;
-    }
-  }
-
-  // Notification Events
-  public record BeforeSendNotificationEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      Namespace namespace,
-      String table,
-      NotificationRequest notificationRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_SEND_NOTIFICATION;
-    }
-  }
-
-  // TODO: Add result once SendNotification API changes are confirmed to return the result.
-  public record AfterSendNotificationEvent(
-      PolarisEventMetadata metadata, String catalogName, Namespace namespace, String table)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_SEND_NOTIFICATION;
-    }
-  }
-
-  // Configuration Events
-  public record BeforeGetConfigEvent(PolarisEventMetadata metadata, String warehouse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_CONFIG;
-    }
-  }
-
-  public record AfterGetConfigEvent(PolarisEventMetadata metadata, ConfigResponse configResponse)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_CONFIG;
-    }
-  }
-
-  // Legacy events
-  public record BeforeCommitViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      TableIdentifier identifier,
-      ViewMetadata metadataBefore,
-      ViewMetadata metadataAfter)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_COMMIT_VIEW;
-    }
-  }
-
-  public record AfterCommitViewEvent(
-      PolarisEventMetadata metadata,
-      String catalogName,
-      TableIdentifier identifier,
-      ViewMetadata metadataBefore,
-      ViewMetadata metadataAfter)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_COMMIT_VIEW;
-    }
-  }
-
-  public record BeforeRefreshTableEvent(
-      PolarisEventMetadata metadata, String catalogName, TableIdentifier tableIdentifier)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REFRESH_TABLE;
-    }
-  }
-
-  public record AfterRefreshTableEvent(
-      PolarisEventMetadata metadata, String catalogName, TableIdentifier tableIdentifier)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REFRESH_TABLE;
-    }
-  }
-
-  public record BeforeRefreshViewEvent(
-      PolarisEventMetadata metadata, String catalogName, TableIdentifier viewIdentifier)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REFRESH_VIEW;
-    }
-  }
-
-  public record AfterRefreshViewEvent(
-      PolarisEventMetadata metadata, String catalogName, TableIdentifier viewIdentifier)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REFRESH_VIEW;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java
index 0c0f337cd..2aaf83483 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java
@@ -18,10 +18,18 @@
  */
 package org.apache.polaris.service.events;
 
-/** Represents an event emitted by Polaris. */
-public interface PolarisEvent {
+/**
+ * Represents an event emitted by Polaris. Events have a type, metadata, and a map of typed
+ * attributes
+ */
+public record PolarisEvent(
+    PolarisEventType type, PolarisEventMetadata metadata, AttributeMap attributes) {
 
-  PolarisEventType type();
+  public PolarisEvent {
+    attributes = new AttributeMap(attributes);
+  }
 
-  PolarisEventMetadata metadata();
+  public PolarisEvent(PolarisEventType type, PolarisEventMetadata metadata) {
+    this(type, metadata, new AttributeMap());
+  }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalRolesServiceEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalRolesServiceEvents.java
deleted file mode 100644
index 2a95cc1f3..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalRolesServiceEvents.java
+++ /dev/null
@@ -1,192 +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.polaris.service.events;
-
-import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
-import org.apache.polaris.core.admin.model.PrincipalRole;
-import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
-
-public class PrincipalRolesServiceEvents {
-  public record AfterCreatePrincipalRoleEvent(
-      PolarisEventMetadata metadata, PrincipalRole principalRole) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeCreatePrincipalRoleEvent(
-      PolarisEventMetadata metadata, CreatePrincipalRoleRequest createPrincipalRoleRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterDeletePrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DELETE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeDeletePrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DELETE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterGetPrincipalRoleEvent(
-      PolarisEventMetadata metadata, PrincipalRole principalRole) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeGetPrincipalRoleEvent(PolarisEventMetadata metadata, String principalRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterUpdatePrincipalRoleEvent(
-      PolarisEventMetadata metadata, PrincipalRole updatedPrincipalRole) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeUpdatePrincipalRoleEvent(
-      PolarisEventMetadata metadata,
-      String principalRoleName,
-      UpdatePrincipalRoleRequest updateRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterListPrincipalRolesEvent(PolarisEventMetadata metadata)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_PRINCIPAL_ROLES;
-    }
-  }
-
-  public record BeforeListPrincipalRolesEvent(PolarisEventMetadata metadata)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_PRINCIPAL_ROLES;
-    }
-  }
-
-  public record AfterAssignCatalogRoleToPrincipalRoleEvent(
-      PolarisEventMetadata metadata,
-      String principalRoleName,
-      String catalogName,
-      String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeAssignCatalogRoleToPrincipalRoleEvent(
-      PolarisEventMetadata metadata,
-      String principalRoleName,
-      String catalogName,
-      String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterRevokeCatalogRoleFromPrincipalRoleEvent(
-      PolarisEventMetadata metadata,
-      String principalRoleName,
-      String catalogName,
-      String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeRevokeCatalogRoleFromPrincipalRoleEvent(
-      PolarisEventMetadata metadata,
-      String principalRoleName,
-      String catalogName,
-      String catalogRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterListAssigneePrincipalsForPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_ASSIGNEE_PRINCIPALS_FOR_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeListAssigneePrincipalsForPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_ASSIGNEE_PRINCIPALS_FOR_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterListCatalogRolesForPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeListCatalogRolesForPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalRoleName, String catalogName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalsServiceEvents.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalsServiceEvents.java
deleted file mode 100644
index 6c461b9e3..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PrincipalsServiceEvents.java
+++ /dev/null
@@ -1,191 +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.polaris.service.events;
-
-import org.apache.polaris.core.admin.model.Principal;
-import org.apache.polaris.core.admin.model.PrincipalRole;
-import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
-
-public class PrincipalsServiceEvents {
-  public record AfterCreatePrincipalEvent(PolarisEventMetadata metadata, Principal principal)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_CREATE_PRINCIPAL;
-    }
-  }
-
-  public record BeforeCreatePrincipalEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_CREATE_PRINCIPAL;
-    }
-  }
-
-  public record AfterDeletePrincipalEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_DELETE_PRINCIPAL;
-    }
-  }
-
-  public record BeforeDeletePrincipalEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_DELETE_PRINCIPAL;
-    }
-  }
-
-  public record AfterGetPrincipalEvent(PolarisEventMetadata metadata, Principal principal)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_GET_PRINCIPAL;
-    }
-  }
-
-  public record BeforeGetPrincipalEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_GET_PRINCIPAL;
-    }
-  }
-
-  public record AfterUpdatePrincipalEvent(PolarisEventMetadata metadata, Principal principal)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_UPDATE_PRINCIPAL;
-    }
-  }
-
-  public record BeforeUpdatePrincipalEvent(
-      PolarisEventMetadata metadata,
-      String principalName,
-      UpdatePrincipalRequest updatePrincipalRequest)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_UPDATE_PRINCIPAL;
-    }
-  }
-
-  public record AfterRotateCredentialsEvent(
-      PolarisEventMetadata metadata, Principal rotatedPrincipal) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_ROTATE_CREDENTIALS;
-    }
-  }
-
-  public record BeforeRotateCredentialsEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_ROTATE_CREDENTIALS;
-    }
-  }
-
-  public record AfterListPrincipalsEvent(PolarisEventMetadata metadata) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_PRINCIPALS;
-    }
-  }
-
-  public record BeforeListPrincipalsEvent(PolarisEventMetadata metadata) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_PRINCIPALS;
-    }
-  }
-
-  public record AfterAssignPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalName, PrincipalRole principalRole)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_ASSIGN_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeAssignPrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalName, PrincipalRole principalRole)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_ASSIGN_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterRevokePrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalName, String principalRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_REVOKE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record BeforeRevokePrincipalRoleEvent(
-      PolarisEventMetadata metadata, String principalName, String principalRoleName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_REVOKE_PRINCIPAL_ROLE;
-    }
-  }
-
-  public record AfterListAssignedPrincipalRolesEvent(
-      PolarisEventMetadata metadata, String principalName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_LIST_ASSIGNED_PRINCIPAL_ROLES;
-    }
-  }
-
-  public record BeforeListAssignedPrincipalRolesEvent(
-      PolarisEventMetadata metadata, String principalName) implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_LIST_ASSIGNED_PRINCIPAL_ROLES;
-    }
-  }
-
-  public record BeforeResetCredentialsEvent(PolarisEventMetadata metadata, String principalName)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.BEFORE_RESET_CREDENTIALS;
-    }
-  }
-
-  public record AfterResetCredentialsEvent(PolarisEventMetadata metadata, Principal principal)
-      implements PolarisEvent {
-    @Override
-    public PolarisEventType type() {
-      return PolarisEventType.AFTER_RESET_CREDENTIALS;
-    }
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/PropertyMapEventListener.java b/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/PropertyMapEventListener.java
deleted file mode 100644
index 8cd01934c..000000000
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/PropertyMapEventListener.java
+++ /dev/null
@@ -1,46 +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.polaris.service.events.jsonEventListener;
-
-import java.util.HashMap;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
-import org.apache.polaris.service.events.PolarisEventMetadata;
-import org.apache.polaris.service.events.listeners.PolarisEventListener;
-
-/**
- * This class provides a common framework for transforming Polaris events into a HashMap, which can
- * be used to transform the event further, such as transforming into a JSON string, and send them to
- * various destinations. Concrete implementations should override the
- * {{@code @link#transformAndSendEvent(HashMap)}} method to define how the event data should be
- * transformed into a JSON string, transmitted, and/or stored.
- */
-public abstract class PropertyMapEventListener implements PolarisEventListener {
-
-  protected abstract void transformAndSendEvent(
-      HashMap<String, Object> properties, PolarisEventMetadata metadata);
-
-  @Override
-  public void onAfterRefreshTable(IcebergRestCatalogEvents.AfterRefreshTableEvent event) {
-    HashMap<String, Object> properties = new HashMap<>();
-    properties.put("event_type", event.getClass().getSimpleName());
-    properties.put("table_identifier", event.tableIdentifier().toString());
-    transformAndSendEvent(properties, event.metadata());
-  }
-}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListener.java b/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListener.java
index bd7d2baf0..291b66c89 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListener.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListener.java
@@ -31,8 +31,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Supplier;
-import org.apache.polaris.service.events.PolarisEventMetadata;
-import org.apache.polaris.service.events.jsonEventListener.PropertyMapEventListener;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
+import org.apache.polaris.service.events.PolarisEventType;
+import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.regions.Region;
@@ -47,7 +50,7 @@ import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsResponse
 
 @ApplicationScoped
 @Identifier("aws-cloudwatch")
-public class AwsCloudWatchEventListener extends PropertyMapEventListener {
+public class AwsCloudWatchEventListener implements PolarisEventListener {
   private static final Logger LOGGER = LoggerFactory.getLogger(AwsCloudWatchEventListener.class);
 
   private CloudWatchLogsAsyncClient client;
@@ -142,17 +145,29 @@ public class AwsCloudWatchEventListener extends PropertyMapEventListener {
   }
 
   @Override
-  protected void transformAndSendEvent(
-      HashMap<String, Object> properties, PolarisEventMetadata metadata) {
-    properties.put("realm_id", metadata.realmId());
-    metadata
+  public void onEvent(PolarisEvent event) {
+    if (event.type() != PolarisEventType.AFTER_REFRESH_TABLE) {
+      return;
+    }
+    HashMap<String, Object> properties = new HashMap<>();
+    properties.put("event_type", event.type().name());
+    event
+        .attributes()
+        .get(EventAttributes.TABLE_IDENTIFIER)
+        .map(TableIdentifier::toString)
+        .ifPresent(id -> properties.put("table_identifier", id));
+
+    properties.put("realm_id", event.metadata().realmId());
+    event
+        .metadata()
         .user()
         .ifPresent(
             p -> {
               properties.put("principal", p.getName());
               properties.put("activated_roles", p.getRoles());
             });
-    metadata.requestId().ifPresent(id -> properties.put("request_id", id));
+    event.metadata().requestId().ifPresent(id -> properties.put("request_id", id));
+
     String eventAsJson;
     try {
       eventAsJson = objectMapper.writeValueAsString(properties);
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisEventListener.java b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisEventListener.java
index 962e63623..2bdb05ddf 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisEventListener.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisEventListener.java
@@ -18,410 +18,14 @@
  */
 package org.apache.polaris.service.events.listeners;
 
-import org.apache.polaris.service.events.AfterAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeLimitRequestRateEvent;
-import org.apache.polaris.service.events.CatalogGenericTableServiceEvents;
-import org.apache.polaris.service.events.CatalogPolicyServiceEvents;
-import org.apache.polaris.service.events.CatalogsServiceEvents;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
-import org.apache.polaris.service.events.PrincipalRolesServiceEvents;
-import org.apache.polaris.service.events.PrincipalsServiceEvents;
+import org.apache.polaris.service.events.PolarisEvent;
 
 /**
- * Represents an event listener that can respond to notable moments during Polaris's execution.
- * Event details are documented under the event objects themselves.
+ * Event listener that responds to notable moments during Polaris's execution. Implementations can
+ * filter events by checking {@link PolarisEvent#type()} or by querying attributes with {@link
+ * PolarisEvent#hasAttribute} and {@link PolarisEvent#attribute}.
  */
 public interface PolarisEventListener {
 
-  /*
-   API Compatibility Note: any new methods added to this interface must have a default
-   implementation in order to preserve binary compatibility.
-  */
-
-  // ============= Catalog Events =============
-
-  default void onBeforeCreateCatalog(CatalogsServiceEvents.BeforeCreateCatalogEvent event) {}
-
-  default void onAfterCreateCatalog(CatalogsServiceEvents.AfterCreateCatalogEvent event) {}
-
-  default void onBeforeDeleteCatalog(CatalogsServiceEvents.BeforeDeleteCatalogEvent event) {}
-
-  default void onAfterDeleteCatalog(CatalogsServiceEvents.AfterDeleteCatalogEvent event) {}
-
-  default void onBeforeGetCatalog(CatalogsServiceEvents.BeforeGetCatalogEvent event) {}
-
-  default void onAfterGetCatalog(CatalogsServiceEvents.AfterGetCatalogEvent event) {}
-
-  default void onBeforeUpdateCatalog(CatalogsServiceEvents.BeforeUpdateCatalogEvent event) {}
-
-  default void onAfterUpdateCatalog(CatalogsServiceEvents.AfterUpdateCatalogEvent event) {}
-
-  default void onBeforeListCatalogs(CatalogsServiceEvents.BeforeListCatalogsEvent event) {}
-
-  default void onAfterListCatalogs(CatalogsServiceEvents.AfterListCatalogsEvent event) {}
-
-  // ============= Principal Events =============
-
-  default void onBeforeCreatePrincipal(PrincipalsServiceEvents.BeforeCreatePrincipalEvent event) {}
-
-  default void onAfterCreatePrincipal(PrincipalsServiceEvents.AfterCreatePrincipalEvent event) {}
-
-  default void onBeforeDeletePrincipal(PrincipalsServiceEvents.BeforeDeletePrincipalEvent event) {}
-
-  default void onAfterDeletePrincipal(PrincipalsServiceEvents.AfterDeletePrincipalEvent event) {}
-
-  default void onBeforeGetPrincipal(PrincipalsServiceEvents.BeforeGetPrincipalEvent event) {}
-
-  default void onAfterGetPrincipal(PrincipalsServiceEvents.AfterGetPrincipalEvent event) {}
-
-  default void onBeforeUpdatePrincipal(PrincipalsServiceEvents.BeforeUpdatePrincipalEvent event) {}
-
-  default void onAfterUpdatePrincipal(PrincipalsServiceEvents.AfterUpdatePrincipalEvent event) {}
-
-  default void onBeforeRotateCredentials(
-      PrincipalsServiceEvents.BeforeRotateCredentialsEvent event) {}
-
-  default void onAfterRotateCredentials(
-      PrincipalsServiceEvents.AfterRotateCredentialsEvent event) {}
-
-  default void onBeforeListPrincipals(PrincipalsServiceEvents.BeforeListPrincipalsEvent event) {}
-
-  default void onAfterListPrincipals(PrincipalsServiceEvents.AfterListPrincipalsEvent event) {}
-
-  default void onBeforeResetCredentials(
-      PrincipalsServiceEvents.BeforeResetCredentialsEvent event) {}
-
-  default void onAfterResetCredentials(PrincipalsServiceEvents.AfterResetCredentialsEvent event) {}
-
-  default void onBeforeAssignPrincipalRole(
-      PrincipalsServiceEvents.BeforeAssignPrincipalRoleEvent event) {}
-
-  default void onAfterAssignPrincipalRole(
-      PrincipalsServiceEvents.AfterAssignPrincipalRoleEvent event) {}
-
-  default void onBeforeRevokePrincipalRole(
-      PrincipalsServiceEvents.BeforeRevokePrincipalRoleEvent event) {}
-
-  default void onAfterRevokePrincipalRole(
-      PrincipalsServiceEvents.AfterRevokePrincipalRoleEvent event) {}
-
-  default void onBeforeListAssignedPrincipalRoles(
-      PrincipalsServiceEvents.BeforeListAssignedPrincipalRolesEvent event) {}
-
-  default void onAfterListAssignedPrincipalRoles(
-      PrincipalsServiceEvents.AfterListAssignedPrincipalRolesEvent event) {}
-
-  // ============= Principal Role Events =============
-
-  default void onBeforeCreatePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeCreatePrincipalRoleEvent event) {}
-
-  default void onAfterCreatePrincipalRole(
-      PrincipalRolesServiceEvents.AfterCreatePrincipalRoleEvent event) {}
-
-  default void onBeforeDeletePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeDeletePrincipalRoleEvent event) {}
-
-  default void onAfterDeletePrincipalRole(
-      PrincipalRolesServiceEvents.AfterDeletePrincipalRoleEvent event) {}
-
-  default void onBeforeGetPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeGetPrincipalRoleEvent event) {}
-
-  default void onAfterGetPrincipalRole(
-      PrincipalRolesServiceEvents.AfterGetPrincipalRoleEvent event) {}
-
-  default void onBeforeUpdatePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeUpdatePrincipalRoleEvent event) {}
-
-  default void onAfterUpdatePrincipalRole(
-      PrincipalRolesServiceEvents.AfterUpdatePrincipalRoleEvent event) {}
-
-  default void onBeforeListPrincipalRoles(
-      PrincipalRolesServiceEvents.BeforeListPrincipalRolesEvent event) {}
-
-  default void onAfterListPrincipalRoles(
-      PrincipalRolesServiceEvents.AfterListPrincipalRolesEvent event) {}
-
-  // ============= Catalog Role Events =============
-
-  default void onBeforeCreateCatalogRole(
-      CatalogsServiceEvents.BeforeCreateCatalogRoleEvent event) {}
-
-  default void onAfterCreateCatalogRole(CatalogsServiceEvents.AfterCreateCatalogRoleEvent event) {}
-
-  default void onBeforeDeleteCatalogRole(
-      CatalogsServiceEvents.BeforeDeleteCatalogRoleEvent event) {}
-
-  default void onAfterDeleteCatalogRole(CatalogsServiceEvents.AfterDeleteCatalogRoleEvent event) {}
-
-  default void onBeforeGetCatalogRole(CatalogsServiceEvents.BeforeGetCatalogRoleEvent event) {}
-
-  default void onAfterGetCatalogRole(CatalogsServiceEvents.AfterGetCatalogRoleEvent event) {}
-
-  default void onBeforeUpdateCatalogRole(
-      CatalogsServiceEvents.BeforeUpdateCatalogRoleEvent event) {}
-
-  default void onAfterUpdateCatalogRole(CatalogsServiceEvents.AfterUpdateCatalogRoleEvent event) {}
-
-  default void onBeforeListCatalogRoles(CatalogsServiceEvents.BeforeListCatalogRolesEvent event) {}
-
-  default void onAfterListCatalogRoles(CatalogsServiceEvents.AfterListCatalogRolesEvent event) {}
-
-  default void onBeforeAssignCatalogRoleToPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeAssignCatalogRoleToPrincipalRoleEvent event) {}
-
-  default void onAfterAssignCatalogRoleToPrincipalRole(
-      PrincipalRolesServiceEvents.AfterAssignCatalogRoleToPrincipalRoleEvent event) {}
-
-  default void onBeforeRevokeCatalogRoleFromPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeRevokeCatalogRoleFromPrincipalRoleEvent event) {}
-
-  default void onAfterRevokeCatalogRoleFromPrincipalRole(
-      PrincipalRolesServiceEvents.AfterRevokeCatalogRoleFromPrincipalRoleEvent event) {}
-
-  default void onBeforeListAssigneePrincipalsForPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeListAssigneePrincipalsForPrincipalRoleEvent event) {}
-
-  default void onAfterListAssigneePrincipalsForPrincipalRole(
-      PrincipalRolesServiceEvents.AfterListAssigneePrincipalsForPrincipalRoleEvent event) {}
-
-  default void onBeforeListCatalogRolesForPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeListCatalogRolesForPrincipalRoleEvent event) {}
-
-  default void onAfterListCatalogRolesForPrincipalRole(
-      PrincipalRolesServiceEvents.AfterListCatalogRolesForPrincipalRoleEvent event) {}
-
-  default void onBeforeAddGrantToCatalogRole(
-      CatalogsServiceEvents.BeforeAddGrantToCatalogRoleEvent event) {}
-
-  default void onAfterAddGrantToCatalogRole(
-      CatalogsServiceEvents.AfterAddGrantToCatalogRoleEvent event) {}
-
-  default void onBeforeRevokeGrantFromCatalogRole(
-      CatalogsServiceEvents.BeforeRevokeGrantFromCatalogRoleEvent event) {}
-
-  default void onAfterRevokeGrantFromCatalogRole(
-      CatalogsServiceEvents.AfterRevokeGrantFromCatalogRoleEvent event) {}
-
-  default void onBeforeListAssigneePrincipalRolesForCatalogRole(
-      CatalogsServiceEvents.BeforeListAssigneePrincipalRolesForCatalogRoleEvent event) {}
-
-  default void onAfterListAssigneePrincipalRolesForCatalogRole(
-      CatalogsServiceEvents.AfterListAssigneePrincipalRolesForCatalogRoleEvent event) {}
-
-  default void onBeforeListGrantsForCatalogRole(
-      CatalogsServiceEvents.BeforeListGrantsForCatalogRoleEvent event) {}
-
-  default void onAfterListGrantsForCatalogRole(
-      CatalogsServiceEvents.AfterListGrantsForCatalogRoleEvent event) {}
-
-  // ============= Iceberg REST Namespace Events =============
-
-  default void onBeforeCreateNamespace(IcebergRestCatalogEvents.BeforeCreateNamespaceEvent event) {}
-
-  default void onAfterCreateNamespace(IcebergRestCatalogEvents.AfterCreateNamespaceEvent event) {}
-
-  default void onBeforeListNamespaces(IcebergRestCatalogEvents.BeforeListNamespacesEvent event) {}
-
-  default void onAfterListNamespaces(IcebergRestCatalogEvents.AfterListNamespacesEvent event) {}
-
-  default void onBeforeLoadNamespaceMetadata(
-      IcebergRestCatalogEvents.BeforeLoadNamespaceMetadataEvent event) {}
-
-  default void onAfterLoadNamespaceMetadata(
-      IcebergRestCatalogEvents.AfterLoadNamespaceMetadataEvent event) {}
-
-  default void onBeforeCheckExistsNamespace(
-      IcebergRestCatalogEvents.BeforeCheckExistsNamespaceEvent event) {}
-
-  default void onAfterCheckExistsNamespace(
-      IcebergRestCatalogEvents.AfterCheckExistsNamespaceEvent event) {}
-
-  default void onBeforeDropNamespace(IcebergRestCatalogEvents.BeforeDropNamespaceEvent event) {}
-
-  default void onAfterDropNamespace(IcebergRestCatalogEvents.AfterDropNamespaceEvent event) {}
-
-  default void onBeforeUpdateNamespaceProperties(
-      IcebergRestCatalogEvents.BeforeUpdateNamespacePropertiesEvent event) {}
-
-  default void onAfterUpdateNamespaceProperties(
-      IcebergRestCatalogEvents.AfterUpdateNamespacePropertiesEvent event) {}
-
-  // ============= Iceberg REST Table Events =============
-
-  default void onBeforeCreateTable(IcebergRestCatalogEvents.BeforeCreateTableEvent event) {}
-
-  default void onAfterCreateTable(IcebergRestCatalogEvents.AfterCreateTableEvent event) {}
-
-  default void onBeforeRefreshTable(IcebergRestCatalogEvents.BeforeRefreshTableEvent event) {}
-
-  default void onAfterRefreshTable(IcebergRestCatalogEvents.AfterRefreshTableEvent event) {}
-
-  default void onBeforeListTables(IcebergRestCatalogEvents.BeforeListTablesEvent event) {}
-
-  default void onAfterListTables(IcebergRestCatalogEvents.AfterListTablesEvent event) {}
-
-  default void onBeforeLoadTable(IcebergRestCatalogEvents.BeforeLoadTableEvent event) {}
-
-  default void onAfterLoadTable(IcebergRestCatalogEvents.AfterLoadTableEvent event) {}
-
-  default void onBeforeCheckExistsTable(
-      IcebergRestCatalogEvents.BeforeCheckExistsTableEvent event) {}
-
-  default void onAfterCheckExistsTable(IcebergRestCatalogEvents.AfterCheckExistsTableEvent event) {}
-
-  default void onBeforeDropTable(IcebergRestCatalogEvents.BeforeDropTableEvent event) {}
-
-  default void onAfterDropTable(IcebergRestCatalogEvents.AfterDropTableEvent event) {}
-
-  default void onBeforeRegisterTable(IcebergRestCatalogEvents.BeforeRegisterTableEvent event) {}
-
-  default void onAfterRegisterTable(IcebergRestCatalogEvents.AfterRegisterTableEvent event) {}
-
-  default void onBeforeRenameTable(IcebergRestCatalogEvents.BeforeRenameTableEvent event) {}
-
-  default void onAfterRenameTable(IcebergRestCatalogEvents.AfterRenameTableEvent event) {}
-
-  default void onBeforeUpdateTable(IcebergRestCatalogEvents.BeforeUpdateTableEvent event) {}
-
-  default void onAfterUpdateTable(IcebergRestCatalogEvents.AfterUpdateTableEvent event) {}
-
-  // ============ Iceberg REST View Events =============
-
-  default void onBeforeCreateView(IcebergRestCatalogEvents.BeforeCreateViewEvent event) {}
-
-  default void onAfterCreateView(IcebergRestCatalogEvents.AfterCreateViewEvent event) {}
-
-  default void onBeforeCommitView(IcebergRestCatalogEvents.BeforeCommitViewEvent event) {}
-
-  default void onAfterCommitView(IcebergRestCatalogEvents.AfterCommitViewEvent event) {}
-
-  default void onBeforeRefreshView(IcebergRestCatalogEvents.BeforeRefreshViewEvent event) {}
-
-  default void onAfterRefreshView(IcebergRestCatalogEvents.AfterRefreshViewEvent event) {}
-
-  default void onBeforeListViews(IcebergRestCatalogEvents.BeforeListViewsEvent event) {}
-
-  default void onAfterListViews(IcebergRestCatalogEvents.AfterListViewsEvent event) {}
-
-  default void onBeforeLoadView(IcebergRestCatalogEvents.BeforeLoadViewEvent event) {}
-
-  default void onAfterLoadView(IcebergRestCatalogEvents.AfterLoadViewEvent event) {}
-
-  default void onBeforeCheckExistsView(IcebergRestCatalogEvents.BeforeCheckExistsViewEvent event) {}
-
-  default void onAfterCheckExistsView(IcebergRestCatalogEvents.AfterCheckExistsViewEvent event) {}
-
-  default void onBeforeDropView(IcebergRestCatalogEvents.BeforeDropViewEvent event) {}
-
-  default void onAfterDropView(IcebergRestCatalogEvents.AfterDropViewEvent event) {}
-
-  default void onBeforeRenameView(IcebergRestCatalogEvents.BeforeRenameViewEvent event) {}
-
-  default void onAfterRenameView(IcebergRestCatalogEvents.AfterRenameViewEvent event) {}
-
-  default void onBeforeReplaceView(IcebergRestCatalogEvents.BeforeReplaceViewEvent event) {}
-
-  default void onAfterReplaceView(IcebergRestCatalogEvents.AfterReplaceViewEvent event) {}
-
-  // ============ Iceberg REST Credential Events =============
-
-  default void onBeforeLoadCredentials(IcebergRestCatalogEvents.BeforeLoadCredentialsEvent event) {}
-
-  default void onAfterLoadCredentials(IcebergRestCatalogEvents.AfterLoadCredentialsEvent event) {}
-
-  // ============ Iceberg REST Transaction Events =============
-
-  default void onBeforeCommitTransaction(
-      IcebergRestCatalogEvents.BeforeCommitTransactionEvent event) {}
-
-  default void onAfterCommitTransaction(
-      IcebergRestCatalogEvents.AfterCommitTransactionEvent event) {}
-
-  // ============ Iceberg REST Notification Events =============
-
-  default void onBeforeSendNotification(
-      IcebergRestCatalogEvents.BeforeSendNotificationEvent event) {}
-
-  default void onAfterSendNotification(IcebergRestCatalogEvents.AfterSendNotificationEvent event) {}
-
-  // ============ Iceberg REST Configuration Events =============
-
-  default void onBeforeGetConfig(IcebergRestCatalogEvents.BeforeGetConfigEvent event) {}
-
-  default void onAfterGetConfig(IcebergRestCatalogEvents.AfterGetConfigEvent event) {}
-
-  // ============= Policy Events =============
-
-  default void onBeforeCreatePolicy(CatalogPolicyServiceEvents.BeforeCreatePolicyEvent event) {}
-
-  default void onAfterCreatePolicy(CatalogPolicyServiceEvents.AfterCreatePolicyEvent event) {}
-
-  default void onBeforeListPolicies(CatalogPolicyServiceEvents.BeforeListPoliciesEvent event) {}
-
-  default void onAfterListPolicies(CatalogPolicyServiceEvents.AfterListPoliciesEvent event) {}
-
-  default void onBeforeLoadPolicy(CatalogPolicyServiceEvents.BeforeLoadPolicyEvent event) {}
-
-  default void onAfterLoadPolicy(CatalogPolicyServiceEvents.AfterLoadPolicyEvent event) {}
-
-  default void onBeforeUpdatePolicy(CatalogPolicyServiceEvents.BeforeUpdatePolicyEvent event) {}
-
-  default void onAfterUpdatePolicy(CatalogPolicyServiceEvents.AfterUpdatePolicyEvent event) {}
-
-  default void onBeforeDropPolicy(CatalogPolicyServiceEvents.BeforeDropPolicyEvent event) {}
-
-  default void onAfterDropPolicy(CatalogPolicyServiceEvents.AfterDropPolicyEvent event) {}
-
-  default void onBeforeAttachPolicy(CatalogPolicyServiceEvents.BeforeAttachPolicyEvent event) {}
-
-  default void onAfterAttachPolicy(CatalogPolicyServiceEvents.AfterAttachPolicyEvent event) {}
-
-  default void onBeforeDetachPolicy(CatalogPolicyServiceEvents.BeforeDetachPolicyEvent event) {}
-
-  default void onAfterDetachPolicy(CatalogPolicyServiceEvents.AfterDetachPolicyEvent event) {}
-
-  default void onBeforeGetApplicablePolicies(
-      CatalogPolicyServiceEvents.BeforeGetApplicablePoliciesEvent event) {}
-
-  default void onAfterGetApplicablePolicies(
-      CatalogPolicyServiceEvents.AfterGetApplicablePoliciesEvent event) {}
-
-  // ============= Generic Table Events =============
-
-  default void onBeforeCreateGenericTable(
-      CatalogGenericTableServiceEvents.BeforeCreateGenericTableEvent event) {}
-
-  default void onAfterCreateGenericTable(
-      CatalogGenericTableServiceEvents.AfterCreateGenericTableEvent event) {}
-
-  default void onBeforeDropGenericTable(
-      CatalogGenericTableServiceEvents.BeforeDropGenericTableEvent event) {}
-
-  default void onAfterDropGenericTable(
-      CatalogGenericTableServiceEvents.AfterDropGenericTableEvent event) {}
-
-  default void onBeforeListGenericTables(
-      CatalogGenericTableServiceEvents.BeforeListGenericTablesEvent event) {}
-
-  default void onAfterListGenericTables(
-      CatalogGenericTableServiceEvents.AfterListGenericTablesEvent event) {}
-
-  default void onBeforeLoadGenericTable(
-      CatalogGenericTableServiceEvents.BeforeLoadGenericTableEvent event) {}
-
-  default void onAfterLoadGenericTable(
-      CatalogGenericTableServiceEvents.AfterLoadGenericTableEvent event) {}
-
-  // ============= Task Execution Events =============
-
-  default void onBeforeAttemptTask(BeforeAttemptTaskEvent event) {}
-
-  default void onAfterAttemptTask(AfterAttemptTaskEvent event) {}
-
-  // ============= Rate Limiting Events =============
-
-  default void onBeforeLimitRequestRate(BeforeLimitRequestRateEvent event) {}
+  default void onEvent(PolarisEvent event) {}
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java
index 16f85589e..e073e9344 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/listeners/PolarisPersistenceEventListener.java
@@ -23,29 +23,45 @@ import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import org.apache.iceberg.TableMetadata;
 import org.apache.iceberg.TableMetadataParser;
+import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.polaris.core.admin.model.Catalog;
 import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.entity.PolarisEvent;
-import org.apache.polaris.service.events.CatalogsServiceEvents;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 
 public abstract class PolarisPersistenceEventListener implements PolarisEventListener {
 
-  // TODO: Ensure all events (except RateLimiter ones) call `processEvent`
-
   @Override
-  public void onAfterCreateTable(IcebergRestCatalogEvents.AfterCreateTableEvent event) {
-    TableMetadata tableMetadata = event.loadTableResponse().tableMetadata();
-    PolarisEvent polarisEvent =
-        new PolarisEvent(
-            event.catalogName(),
+  public void onEvent(PolarisEvent event) {
+    switch (event.type()) {
+      case AFTER_CREATE_TABLE -> handleAfterCreateTable(event);
+      case AFTER_CREATE_CATALOG -> handleAfterCreateCatalog(event);
+      default -> {
+        // Other events not handled by this listener
+      }
+    }
+  }
+
+  private void handleAfterCreateTable(PolarisEvent event) {
+    LoadTableResponse loadTableResponse =
+        event.attributes().getRequired(EventAttributes.LOAD_TABLE_RESPONSE);
+    TableMetadata tableMetadata = loadTableResponse.tableMetadata();
+    String catalogName = event.attributes().getRequired(EventAttributes.CATALOG_NAME);
+    Namespace namespace = event.attributes().getRequired(EventAttributes.NAMESPACE);
+    String tableName = event.attributes().getRequired(EventAttributes.TABLE_NAME);
+
+    org.apache.polaris.core.entity.PolarisEvent polarisEvent =
+        new org.apache.polaris.core.entity.PolarisEvent(
+            catalogName,
             event.metadata().eventId().toString(),
             event.metadata().requestId().orElse(null),
-            event.getClass().getSimpleName(),
+            event.type().name(),
             event.metadata().timestamp().toEpochMilli(),
             event.metadata().user().map(PolarisPrincipal::getName).orElse(null),
-            PolarisEvent.ResourceType.TABLE,
-            TableIdentifier.of(event.namespace(), event.tableName()).toString());
+            org.apache.polaris.core.entity.PolarisEvent.ResourceType.TABLE,
+            TableIdentifier.of(namespace, tableName).toString());
     var additionalParameters =
         ImmutableMap.<String, String>builder()
             .put("table-uuid", tableMetadata.uuid())
@@ -55,18 +71,18 @@ public abstract class PolarisPersistenceEventListener implements PolarisEventLis
     processEvent(event.metadata().realmId(), polarisEvent);
   }
 
-  @Override
-  public void onAfterCreateCatalog(CatalogsServiceEvents.AfterCreateCatalogEvent event) {
-    PolarisEvent polarisEvent =
-        new PolarisEvent(
-            event.catalog().getName(),
+  private void handleAfterCreateCatalog(PolarisEvent event) {
+    Catalog catalog = event.attributes().getRequired(EventAttributes.CATALOG);
+    org.apache.polaris.core.entity.PolarisEvent polarisEvent =
+        new org.apache.polaris.core.entity.PolarisEvent(
+            catalog.getName(),
             event.metadata().eventId().toString(),
             event.metadata().requestId().orElse(null),
-            event.getClass().getSimpleName(),
+            event.type().name(),
             event.metadata().timestamp().toEpochMilli(),
             event.metadata().user().map(PolarisPrincipal::getName).orElse(null),
-            PolarisEvent.ResourceType.CATALOG,
-            event.catalog().getName());
+            org.apache.polaris.core.entity.PolarisEvent.ResourceType.CATALOG,
+            catalog.getName());
     Map<String, String> openTelemetryContext = event.metadata().openTelemetryContext();
     if (!openTelemetryContext.isEmpty()) {
       polarisEvent.setAdditionalProperties(openTelemetryContext);
@@ -74,5 +90,6 @@ public abstract class PolarisPersistenceEventListener implements PolarisEventLis
     processEvent(event.metadata().realmId(), polarisEvent);
   }
 
-  protected abstract void processEvent(String realmId, PolarisEvent event);
+  protected abstract void processEvent(
+      String realmId, org.apache.polaris.core.entity.PolarisEvent event);
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java b/runtime/service/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java
index a02da5dd7..d2884b32c 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/ratelimiter/RateLimiterFilter.java
@@ -28,8 +28,11 @@ import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 import java.io.IOException;
 import org.apache.polaris.service.config.FilterPriorities;
-import org.apache.polaris.service.events.BeforeLimitRequestRateEvent;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -60,11 +63,14 @@ public class RateLimiterFilter implements ContainerRequestFilter {
   @Override
   public void filter(ContainerRequestContext ctx) throws IOException {
     if (!rateLimiter.canProceed()) {
-      polarisEventListener.onBeforeLimitRequestRate(
-          new BeforeLimitRequestRateEvent(
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.BEFORE_LIMIT_REQUEST_RATE,
               eventMetadataFactory.create(),
-              ctx.getMethod(),
-              ctx.getUriInfo().getAbsolutePath().toString()));
+              new AttributeMap()
+                  .put(EventAttributes.HTTP_METHOD, ctx.getMethod())
+                  .put(
+                      EventAttributes.REQUEST_URI, ctx.getUriInfo().getAbsolutePath().toString())));
       ctx.abortWith(Response.status(Response.Status.TOO_MANY_REQUESTS).build());
       LOGGER.atDebug().log("Rate limiting request");
     }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/task/TaskExecutorImpl.java b/runtime/service/src/main/java/org/apache/polaris/service/task/TaskExecutorImpl.java
index bad53cde1..99d8e6bab 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/task/TaskExecutorImpl.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/task/TaskExecutorImpl.java
@@ -50,10 +50,12 @@ import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.service.context.catalog.PolarisPrincipalHolder;
 import org.apache.polaris.service.context.catalog.RealmContextHolder;
-import org.apache.polaris.service.events.AfterAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeAttemptTaskEvent;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadata;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.tracing.TracingFilter;
 import org.slf4j.Logger;
@@ -196,9 +198,13 @@ public class TaskExecutorImpl implements TaskExecutor {
 
   protected void handleTask(
       long taskEntityId, CallContext ctx, PolarisEventMetadata eventMetadata, int attempt) {
-    polarisEventListener.onBeforeAttemptTask(
-        new BeforeAttemptTaskEvent(
-            eventMetadataFactory.copy(eventMetadata), taskEntityId, attempt));
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_ATTEMPT_TASK,
+            eventMetadataFactory.copy(eventMetadata),
+            new AttributeMap()
+                .put(EventAttributes.TASK_ENTITY_ID, taskEntityId)
+                .put(EventAttributes.TASK_ATTEMPT, attempt)));
 
     boolean success = false;
     try {
@@ -241,9 +247,14 @@ public class TaskExecutorImpl implements TaskExecutor {
             .log("Unable to execute async task");
       }
     } finally {
-      polarisEventListener.onAfterAttemptTask(
-          new AfterAttemptTaskEvent(
-              eventMetadataFactory.copy(eventMetadata), taskEntityId, attempt, success));
+      polarisEventListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_ATTEMPT_TASK,
+              eventMetadataFactory.copy(eventMetadata),
+              new AttributeMap()
+                  .put(EventAttributes.TASK_ENTITY_ID, taskEntityId)
+                  .put(EventAttributes.TASK_ATTEMPT, attempt)
+                  .put(EventAttributes.TASK_SUCCESS, success)));
     }
   }
 
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
index 6cf14d45a..2f4fbfd18 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
@@ -139,8 +139,10 @@ import org.apache.polaris.service.catalog.io.FileIOFactory;
 import org.apache.polaris.service.catalog.io.MeasuredFileIOFactory;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
 import org.apache.polaris.service.config.ReservedProperties;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.apache.polaris.service.exception.FakeAzureHttpResponse;
@@ -2362,13 +2364,17 @@ public abstract class AbstractIcebergCatalogTest extends CatalogTests<IcebergCat
     table.updateProperties().set(key, valOld).commit();
     table.updateProperties().set(key, valNew).commit();
 
-    var beforeRefreshEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.BeforeRefreshTableEvent.class);
-    Assertions.assertThat(beforeRefreshEvent.tableIdentifier()).isEqualTo(TestData.TABLE);
-
-    var afterRefreshEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.AfterRefreshTableEvent.class);
-    Assertions.assertThat(afterRefreshEvent.tableIdentifier()).isEqualTo(TestData.TABLE);
+    PolarisEvent beforeRefreshEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.BEFORE_REFRESH_TABLE);
+    Assertions.assertThat(
+            beforeRefreshEvent.attributes().getRequired(EventAttributes.TABLE_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
+
+    PolarisEvent afterRefreshEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.AFTER_REFRESH_TABLE);
+    Assertions.assertThat(
+            afterRefreshEvent.attributes().getRequired(EventAttributes.TABLE_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
   }
 
   private static PageToken nextRequest(Page<?> previousPage) {
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
index 9d6f8a8cd..b5a6c4a24 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
@@ -58,8 +58,10 @@ import org.apache.polaris.service.catalog.Profiles;
 import org.apache.polaris.service.catalog.io.FileIOFactory;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
 import org.apache.polaris.service.config.ReservedProperties;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadataFactory;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl;
@@ -254,27 +256,52 @@ public abstract class AbstractIcebergCatalogViewTest extends ViewCatalogTests<Ic
     view.updateProperties().set(key, valOld).commit();
     view.updateProperties().set(key, valNew).commit();
 
-    var beforeRefreshEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.BeforeRefreshViewEvent.class);
-    Assertions.assertThat(beforeRefreshEvent.viewIdentifier()).isEqualTo(TestData.TABLE);
+    PolarisEvent beforeRefreshEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.BEFORE_REFRESH_VIEW);
+    Assertions.assertThat(
+            beforeRefreshEvent.attributes().getRequired(EventAttributes.VIEW_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
 
-    var afterRefreshEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.AfterRefreshViewEvent.class);
-    Assertions.assertThat(afterRefreshEvent.viewIdentifier()).isEqualTo(TestData.TABLE);
+    PolarisEvent afterRefreshEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.AFTER_REFRESH_VIEW);
+    Assertions.assertThat(
+            afterRefreshEvent.attributes().getRequired(EventAttributes.VIEW_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
 
-    var beforeCommitEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.BeforeCommitViewEvent.class);
-    Assertions.assertThat(beforeCommitEvent.identifier()).isEqualTo(TestData.TABLE);
-    Assertions.assertThat(beforeCommitEvent.metadataBefore().properties().get(key))
-        .isEqualTo(valOld);
-    Assertions.assertThat(beforeCommitEvent.metadataAfter().properties().get(key))
-        .isEqualTo(valNew);
+    PolarisEvent beforeCommitEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.BEFORE_COMMIT_VIEW);
+    Assertions.assertThat(
+            beforeCommitEvent.attributes().getRequired(EventAttributes.VIEW_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
+    Assertions.assertThat(
+            beforeCommitEvent
+                .attributes()
+                .get(EventAttributes.VIEW_METADATA_BEFORE)
+                .map(m -> m.properties().get(key)))
+        .hasValue(valOld);
+    Assertions.assertThat(
+            beforeCommitEvent
+                .attributes()
+                .get(EventAttributes.VIEW_METADATA_AFTER)
+                .map(m -> m.properties().get(key)))
+        .hasValue(valNew);
 
-    var afterCommitEvent =
-        testPolarisEventListener.getLatest(IcebergRestCatalogEvents.AfterCommitViewEvent.class);
-    Assertions.assertThat(afterCommitEvent.identifier()).isEqualTo(TestData.TABLE);
-    Assertions.assertThat(afterCommitEvent.metadataBefore().properties().get(key))
-        .isEqualTo(valOld);
-    Assertions.assertThat(afterCommitEvent.metadataAfter().properties().get(key)).isEqualTo(valNew);
+    PolarisEvent afterCommitEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.AFTER_COMMIT_VIEW);
+    Assertions.assertThat(
+            afterCommitEvent.attributes().getRequired(EventAttributes.VIEW_IDENTIFIER))
+        .isEqualTo(TestData.TABLE);
+    Assertions.assertThat(
+            afterCommitEvent
+                .attributes()
+                .get(EventAttributes.VIEW_METADATA_BEFORE)
+                .map(m -> m.properties().get(key)))
+        .hasValue(valOld);
+    Assertions.assertThat(
+            afterCommitEvent
+                .attributes()
+                .get(EventAttributes.VIEW_METADATA_AFTER)
+                .map(m -> m.properties().get(key)))
+        .hasValue(valNew);
   }
 }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionEventTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionEventTest.java
index 733723527..e0f4a2783 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionEventTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionEventTest.java
@@ -41,7 +41,9 @@ import org.apache.polaris.core.admin.model.CreateCatalogRequest;
 import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
 import org.apache.polaris.core.admin.model.StorageConfigInfo;
 import org.apache.polaris.service.TestServices;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -75,23 +77,16 @@ public class CommitTransactionEventTest {
     // emitted
     TestPolarisEventListener testEventListener =
         (TestPolarisEventListener) testServices.polarisEventListener();
-    assertThat(
-            testEventListener.getLatest(
-                IcebergRestCatalogEvents.BeforeCommitTransactionEvent.class))
-        .isNotNull();
-    assertThat(
-            testEventListener
-                .getLatest(IcebergRestCatalogEvents.BeforeUpdateTableEvent.class)
-                .sourceTable())
+    assertThat(testEventListener.getLatest(PolarisEventType.BEFORE_COMMIT_TRANSACTION)).isNotNull();
+    PolarisEvent beforeUpdateEvent =
+        testEventListener.getLatest(PolarisEventType.BEFORE_UPDATE_TABLE);
+    assertThat(beforeUpdateEvent.attributes().getRequired(EventAttributes.TABLE_NAME))
         .isEqualTo(table2Name);
 
-    assertThat(
-            testEventListener.getLatest(IcebergRestCatalogEvents.AfterCommitTransactionEvent.class))
-        .isNotNull();
-    assertThat(
-            testEventListener
-                .getLatest(IcebergRestCatalogEvents.AfterUpdateTableEvent.class)
-                .sourceTable())
+    assertThat(testEventListener.getLatest(PolarisEventType.AFTER_COMMIT_TRANSACTION)).isNotNull();
+    PolarisEvent afterUpdateEvent =
+        testEventListener.getLatest(PolarisEventType.AFTER_UPDATE_TABLE);
+    assertThat(afterUpdateEvent.attributes().getRequired(EventAttributes.TABLE_NAME))
         .isEqualTo(table2Name);
   }
 
@@ -110,23 +105,15 @@ public class CommitTransactionEventTest {
 
     // Verify that all BeforeCommitTransaction and BeforeUpdateTable events were emitted,
     // and that the AfterCommitTransaction and AfterUpdateTable events were not emitted
-    assertThat(
-            testEventListener.getLatest(
-                IcebergRestCatalogEvents.BeforeCommitTransactionEvent.class))
-        .isNotNull();
-    assertThat(
-            testEventListener
-                .getLatest(IcebergRestCatalogEvents.BeforeUpdateTableEvent.class)
-                .sourceTable())
+    assertThat(testEventListener.getLatest(PolarisEventType.BEFORE_COMMIT_TRANSACTION)).isNotNull();
+    PolarisEvent beforeUpdateEvent =
+        testEventListener.getLatest(PolarisEventType.BEFORE_UPDATE_TABLE);
+    assertThat(beforeUpdateEvent.attributes().getRequired(EventAttributes.TABLE_NAME))
         .isEqualTo(table4Name);
 
-    assertThatThrownBy(
-            () ->
-                testEventListener.getLatest(
-                    IcebergRestCatalogEvents.AfterCommitTransactionEvent.class))
+    assertThatThrownBy(() -> testEventListener.getLatest(PolarisEventType.AFTER_COMMIT_TRANSACTION))
         .isInstanceOf(IllegalStateException.class);
-    assertThatThrownBy(
-            () -> testEventListener.getLatest(IcebergRestCatalogEvents.AfterUpdateTableEvent.class))
+    assertThatThrownBy(() -> testEventListener.getLatest(PolarisEventType.AFTER_UPDATE_TABLE))
         .isInstanceOf(IllegalStateException.class);
   }
 
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeKeyTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeKeyTest.java
new file mode 100644
index 000000000..e47188ad8
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeKeyTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.polaris.service.events;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.reflect.TypeToken;
+import org.junit.jupiter.api.Test;
+
+class AttributeKeyTest {
+
+  private static final String TEST_KEY = "test_key";
+
+  @Test
+  void testCreateAndAccessors() {
+    AttributeKey<String> key = new AttributeKey<>(TEST_KEY, String.class);
+
+    assertThat(key.name()).isEqualTo(TEST_KEY);
+    assertThat(key.type()).isEqualTo(TypeToken.of(String.class));
+  }
+
+  @Test
+  void testToString() {
+    AttributeKey<String> key = new AttributeKey<>(TEST_KEY, String.class);
+
+    assertThat(key.toString()).contains(TEST_KEY).contains("String");
+  }
+
+  @Test
+  void testNullNameThrows() {
+    assertThatThrownBy(() -> new AttributeKey<>(null, String.class))
+        .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  void testNullTypeThrows() {
+    assertThatThrownBy(() -> new AttributeKey<>(TEST_KEY, (Class<String>) null))
+        .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  void testJsonSerializesToName() throws JsonProcessingException {
+    AttributeKey<String> key = new AttributeKey<>(TEST_KEY, String.class);
+    ObjectMapper mapper = new ObjectMapper();
+
+    String json = mapper.writeValueAsString(key);
+
+    assertThat(json).isEqualTo("\"" + TEST_KEY + "\"");
+  }
+
+  @Test
+  void testAllowedTypes() {
+    assertThat(new AttributeKey<>("s", String.class)).isNotNull();
+    assertThat(new AttributeKey<>("b", Boolean.class)).isNotNull();
+    assertThat(new AttributeKey<>("i", Integer.class)).isNotNull();
+    assertThat(new AttributeKey<>("l", Long.class)).isNotNull();
+    assertThat(new AttributeKey<>("d", Double.class)).isNotNull();
+    assertThat(new AttributeKey<>("list", new TypeToken<java.util.List<String>>() {})).isNotNull();
+    assertThat(new AttributeKey<>("map", new TypeToken<java.util.Map<String, String>>() {}))
+        .isNotNull();
+    assertThat(new AttributeKey<>("set", new TypeToken<java.util.Set<String>>() {})).isNotNull();
+  }
+
+  @Test
+  void testDisallowedTypes() {
+    assertThatThrownBy(() -> new AttributeKey<>("x", java.util.Optional.class))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("not allowed");
+    assertThatThrownBy(() -> new AttributeKey<>("x", java.util.function.Function.class))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("not allowed");
+    assertThatThrownBy(() -> new AttributeKey<>("x", Throwable.class))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("not allowed");
+    assertThatThrownBy(() -> new AttributeKey<>("x", StringBuilder.class))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("not allowed");
+  }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeMapTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeMapTest.java
new file mode 100644
index 000000000..e27444983
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/AttributeMapTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.polaris.service.events;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+class AttributeMapTest {
+
+  private static final AttributeKey<String> STRING_KEY =
+      new AttributeKey<>("string_key", String.class);
+  private static final AttributeKey<Integer> INT_KEY = new AttributeKey<>("int_key", Integer.class);
+
+  @Test
+  void testPutAndGet() {
+    AttributeMap map = new AttributeMap();
+    map.put(STRING_KEY, "value");
+
+    assertThat(map.get(STRING_KEY)).hasValue("value");
+  }
+
+  @Test
+  void testGetReturnsEmptyForMissingKey() {
+    AttributeMap map = new AttributeMap();
+
+    assertThat(map.get(STRING_KEY)).isEmpty();
+  }
+
+  @Test
+  void testGetRequired() {
+    AttributeMap map = new AttributeMap();
+    map.put(STRING_KEY, "value");
+
+    assertThat(map.getRequired(STRING_KEY)).isEqualTo("value");
+  }
+
+  @Test
+  void testGetRequiredThrowsForMissingKey() {
+    AttributeMap map = new AttributeMap();
+
+    assertThatThrownBy(() -> map.getRequired(STRING_KEY))
+        .isInstanceOf(IllegalStateException.class)
+        .hasMessage("Required attribute string_key not found");
+  }
+
+  @Test
+  void testContains() {
+    AttributeMap map = new AttributeMap();
+    map.put(STRING_KEY, "value");
+
+    assertThat(map.contains(STRING_KEY)).isTrue();
+    assertThat(map.contains(INT_KEY)).isFalse();
+  }
+
+  @Test
+  void testNullValueIsIgnored() {
+    AttributeMap map = new AttributeMap();
+    map.put(STRING_KEY, null);
+
+    assertThat(map.contains(STRING_KEY)).isFalse();
+    assertThat(map.isEmpty()).isTrue();
+  }
+
+  @Test
+  void testSize() {
+    AttributeMap map = new AttributeMap();
+    assertThat(map.size()).isEqualTo(0);
+
+    map.put(STRING_KEY, "value");
+    assertThat(map.size()).isEqualTo(1);
+
+    map.put(INT_KEY, 42);
+    assertThat(map.size()).isEqualTo(2);
+  }
+
+  @Test
+  void testIsEmpty() {
+    AttributeMap map = new AttributeMap();
+    assertThat(map.isEmpty()).isTrue();
+
+    map.put(STRING_KEY, "value");
+    assertThat(map.isEmpty()).isFalse();
+  }
+
+  @Test
+  void testCopyConstructor() {
+    AttributeMap original = new AttributeMap();
+    original.put(STRING_KEY, "value");
+
+    AttributeMap copy = new AttributeMap(original);
+
+    assertThat(copy.getRequired(STRING_KEY)).isEqualTo("value");
+
+    // Verify independence
+    original.put(INT_KEY, 42);
+    assertThat(copy.contains(INT_KEY)).isFalse();
+  }
+
+  @Test
+  void testPutReturnsThis() {
+    AttributeMap map = new AttributeMap();
+
+    AttributeMap result = map.put(STRING_KEY, "value");
+
+    assertThat(result).isSameAs(map);
+  }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventTest.java
new file mode 100644
index 000000000..68b1c7d6b
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.polaris.service.events;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class PolarisEventTest {
+
+  private static final String TEST_REALM = "test-realm";
+  private static final String TEST_CATALOG = "my-catalog";
+  private static final String TEST_TABLE = "my-table";
+
+  private static final PolarisEventMetadata TEST_METADATA =
+      ImmutablePolarisEventMetadata.builder().realmId(TEST_REALM).build();
+
+  @Test
+  void testConstructorWithAttributes() {
+    PolarisEvent event =
+        new PolarisEvent(
+            PolarisEventType.BEFORE_CREATE_TABLE,
+            TEST_METADATA,
+            new AttributeMap()
+                .put(EventAttributes.CATALOG_NAME, TEST_CATALOG)
+                .put(EventAttributes.TABLE_NAME, TEST_TABLE));
+
+    assertThat(event.type()).isEqualTo(PolarisEventType.BEFORE_CREATE_TABLE);
+    assertThat(event.metadata()).isEqualTo(TEST_METADATA);
+    assertThat(event.attributes().getRequired(EventAttributes.CATALOG_NAME))
+        .isEqualTo(TEST_CATALOG);
+    assertThat(event.attributes().getRequired(EventAttributes.TABLE_NAME)).isEqualTo(TEST_TABLE);
+  }
+
+  @Test
+  void testConstructorWithoutAttributes() {
+    PolarisEvent event = new PolarisEvent(PolarisEventType.BEFORE_CREATE_TABLE, TEST_METADATA);
+
+    assertThat(event.type()).isEqualTo(PolarisEventType.BEFORE_CREATE_TABLE);
+    assertThat(event.metadata()).isEqualTo(TEST_METADATA);
+    assertThat(event.attributes().isEmpty()).isTrue();
+  }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListenerTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListenerTest.java
index d920655a0..110f146b6 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListenerTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/jsonEventListener/aws/cloudwatch/AwsCloudWatchEventListenerTest.java
@@ -36,8 +36,11 @@ import java.util.concurrent.TimeUnit;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.service.config.PolarisIcebergObjectMapperCustomizer;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
+import org.apache.polaris.service.events.AttributeMap;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadata;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -187,11 +190,13 @@ class AwsCloudWatchEventListenerTest {
     try {
       // Create and send a test event
       TableIdentifier testTable = TableIdentifier.of("test_namespace", "test_table");
-      listener.onAfterRefreshTable(
-          new IcebergRestCatalogEvents.AfterRefreshTableEvent(
+      listener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_REFRESH_TABLE,
               PolarisEventMetadata.builder().realmId(REALM).user(PRINCIPAL).build(),
-              "test_catalog",
-              testTable));
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, "test_catalog")
+                  .put(EventAttributes.TABLE_IDENTIFIER, testTable)));
 
       Awaitility.await("expected amount of records should be sent to CloudWatch")
           .atMost(Duration.ofSeconds(30))
@@ -225,9 +230,7 @@ class AwsCloudWatchEventListenerTest {
               logEvent -> {
                 String message = logEvent.message();
                 assertThat(message).contains(REALM);
-                assertThat(message)
-                    .contains(
-                        IcebergRestCatalogEvents.AfterRefreshTableEvent.class.getSimpleName());
+                assertThat(message).contains(PolarisEventType.AFTER_REFRESH_TABLE.name());
                 assertThat(message).contains(TEST_USER);
                 assertThat(message).contains(testTable.toString());
               });
@@ -249,11 +252,13 @@ class AwsCloudWatchEventListenerTest {
     try {
       // Create and send a test event synchronously
       TableIdentifier syncTestTable = TableIdentifier.of("test_namespace", "test_table_sync");
-      syncListener.onAfterRefreshTable(
-          new IcebergRestCatalogEvents.AfterRefreshTableEvent(
+      syncListener.onEvent(
+          new PolarisEvent(
+              PolarisEventType.AFTER_REFRESH_TABLE,
               PolarisEventMetadata.builder().realmId(REALM).user(PRINCIPAL).build(),
-              "test_catalog",
-              syncTestTable));
+              new AttributeMap()
+                  .put(EventAttributes.CATALOG_NAME, "test_catalog")
+                  .put(EventAttributes.TABLE_IDENTIFIER, syncTestTable)));
 
       Awaitility.await("expected amount of records should be sent to CloudWatch")
           .atMost(Duration.ofSeconds(30))
@@ -289,7 +294,7 @@ class AwsCloudWatchEventListenerTest {
               logEvent -> {
                 String message = logEvent.message();
                 assertThat(message).contains("test_table_sync");
-                assertThat(message).contains("AfterRefreshTableEvent");
+                assertThat(message).contains("AFTER_REFRESH_TABLE");
               });
     } finally {
       // Clean up
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/PolarisEventListenerTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/PolarisEventListenerTest.java
deleted file mode 100644
index 257113d00..000000000
--- a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/PolarisEventListenerTest.java
+++ /dev/null
@@ -1,85 +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.polaris.service.events.listeners;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.polaris.service.events.PolarisEvent;
-import org.jboss.jandex.ClassInfo;
-import org.jboss.jandex.DotName;
-import org.jboss.jandex.Index;
-import org.jboss.jandex.IndexReader;
-import org.junit.jupiter.api.Test;
-
-/** Test that PolarisEventListener interface is well-formed and consistent. */
-class PolarisEventListenerTest {
-
-  @Test
-  void testAllMethodsWellFormed() {
-    for (Method method : PolarisEventListener.class.getMethods()) {
-      assertThat(method.isDefault()).as("Method %s is not default", method.getName()).isTrue();
-      assertThat(method.getReturnType())
-          .as("Method %s does not return void", method.getName())
-          .isEqualTo(void.class);
-      assertThat(method.getParameters())
-          .as("Method %s does not have exactly 1 parameter", method.getName())
-          .hasSize(1);
-      Class<?> eventType = method.getParameters()[0].getType();
-      assertThat(PolarisEvent.class).isAssignableFrom(eventType);
-      String expectedMethodName =
-          "on" + method.getParameterTypes()[0].getSimpleName().replace("Event", "");
-      assertThat(method.getName()).isEqualTo(expectedMethodName);
-    }
-  }
-
-  @Test
-  void testAllEventTypesHandled() throws IOException {
-    Index index;
-    try (InputStream is = getClass().getClassLoader().getResourceAsStream("META-INF/jandex.idx")) {
-      index = new IndexReader(is).read();
-    }
-
-    // Find all classes that implement PolarisEvent
-    Set<String> eventTypes =
-        index.getAllKnownImplementors(DotName.createSimple(PolarisEvent.class)).stream()
-            .map(ClassInfo::name)
-            .map(DotName::toString)
-            .collect(Collectors.toSet());
-
-    assertThat(eventTypes).isNotEmpty();
-
-    // Find all event types that have corresponding listener methods
-    Set<String> handledEventTypes =
-        Arrays.stream(PolarisEventListener.class.getMethods())
-            .filter(method -> method.getParameterCount() == 1)
-            .filter(method -> PolarisEvent.class.isAssignableFrom(method.getParameterTypes()[0]))
-            .map(method -> method.getParameterTypes()[0].getName())
-            .collect(Collectors.toSet());
-
-    // Check that all PolarisEvent implementations have corresponding listener methods
-    assertThat(handledEventTypes).containsAll(eventTypes);
-  }
-}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java
index 2d8e0ec57..f8383f1c4 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/listeners/inmemory/InMemoryBufferEventListenerIntegrationTest.java
@@ -198,7 +198,7 @@ class InMemoryBufferEventListenerIntegrationTest {
     assertThat(e1.getCatalogId()).isEqualTo(catalogName);
     assertThat(e1.getResourceType()).isEqualTo(PolarisEvent.ResourceType.CATALOG);
     assertThat(e1.getResourceIdentifier()).isEqualTo(catalogName);
-    assertThat(e1.getEventType()).isEqualTo("AfterCreateCatalogEvent");
+    assertThat(e1.getEventType()).isEqualTo("AFTER_CREATE_CATALOG");
     assertThat(e1.getPrincipalName()).isEqualTo("root");
     assertThat(e1.getRequestId()).isEqualTo("12345");
     assertThat(e1.getAdditionalPropertiesAsMap())
@@ -211,7 +211,7 @@ class InMemoryBufferEventListenerIntegrationTest {
     assertThat(e2.getCatalogId()).isEqualTo(catalogName);
     assertThat(e2.getResourceType()).isEqualTo(PolarisEvent.ResourceType.TABLE);
     assertThat(e2.getResourceIdentifier()).isEqualTo("db1.t1");
-    assertThat(e2.getEventType()).isEqualTo("AfterCreateTableEvent");
+    assertThat(e2.getEventType()).isEqualTo("AFTER_CREATE_TABLE");
     assertThat(e2.getPrincipalName()).isEqualTo("root");
     assertThat(e2.getRequestId()).isEqualTo("456789");
     assertThat(e2.getAdditionalPropertiesAsMap())
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java b/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
index 7716ecaa3..d6b00a43d 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
@@ -32,7 +32,9 @@ import java.time.Duration;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
-import org.apache.polaris.service.events.BeforeLimitRequestRateEvent;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.PolarisEventListener;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.apache.polaris.service.ratelimiter.RateLimiterFilterTest.Profile;
@@ -148,10 +150,10 @@ public class RateLimiterFilterTest {
     }
     requestAsserter.accept(Status.TOO_MANY_REQUESTS);
 
-    BeforeLimitRequestRateEvent event =
+    PolarisEvent event =
         ((TestPolarisEventListener) polarisEventListener)
-            .getLatest(BeforeLimitRequestRateEvent.class);
-    assertThat(event.method()).isEqualTo("GET");
+            .getLatest(PolarisEventType.BEFORE_LIMIT_REQUEST_RATE);
+    assertThat(event.attributes().getRequired(EventAttributes.HTTP_METHOD)).isEqualTo("GET");
 
     // Examples of expected metrics:
     // http_server_requests_seconds_count{application="Polaris",environment="prod",method="GET",outcome="CLIENT_ERROR",realm_id="org_apache_polaris_service_ratelimiter_RateLimiterFilterTest",status="429",uri="/api/management/v1/principal-roles"} 1.0
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/task/TaskExecutorImplTest.java b/runtime/service/src/test/java/org/apache/polaris/service/task/TaskExecutorImplTest.java
index a75c82bea..d2ddd29dc 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/task/TaskExecutorImplTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/task/TaskExecutorImplTest.java
@@ -26,9 +26,10 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.service.TestServices;
 import org.apache.polaris.service.context.catalog.PolarisPrincipalHolder;
 import org.apache.polaris.service.context.catalog.RealmContextHolder;
-import org.apache.polaris.service.events.AfterAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeAttemptTaskEvent;
+import org.apache.polaris.service.events.EventAttributes;
+import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventMetadata;
+import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -85,10 +86,14 @@ public class TaskExecutorImplTest {
 
           @Override
           public boolean handleTask(TaskEntity task, CallContext callContext) {
-            var beforeTaskAttemptedEvent =
-                testPolarisEventListener.getLatest(BeforeAttemptTaskEvent.class);
-            Assertions.assertEquals(taskEntity.getId(), beforeTaskAttemptedEvent.taskEntityId());
-            Assertions.assertEquals(attempt, beforeTaskAttemptedEvent.attempt());
+            PolarisEvent beforeTaskAttemptedEvent =
+                testPolarisEventListener.getLatest(PolarisEventType.BEFORE_ATTEMPT_TASK);
+            Assertions.assertEquals(
+                taskEntity.getId(),
+                beforeTaskAttemptedEvent.attributes().getRequired(EventAttributes.TASK_ENTITY_ID));
+            Assertions.assertEquals(
+                attempt,
+                beforeTaskAttemptedEvent.attributes().getRequired(EventAttributes.TASK_ATTEMPT));
             return true;
           }
         });
@@ -99,9 +104,14 @@ public class TaskExecutorImplTest {
         PolarisEventMetadata.builder().realmId(realm).build(),
         attempt);
 
-    var afterAttemptTaskEvent = testPolarisEventListener.getLatest(AfterAttemptTaskEvent.class);
-    Assertions.assertEquals(taskEntity.getId(), afterAttemptTaskEvent.taskEntityId());
-    Assertions.assertEquals(attempt, afterAttemptTaskEvent.attempt());
-    Assertions.assertTrue(afterAttemptTaskEvent.success());
+    PolarisEvent afterAttemptTaskEvent =
+        testPolarisEventListener.getLatest(PolarisEventType.AFTER_ATTEMPT_TASK);
+    Assertions.assertEquals(
+        taskEntity.getId(),
+        afterAttemptTaskEvent.attributes().getRequired(EventAttributes.TASK_ENTITY_ID));
+    Assertions.assertEquals(
+        attempt, afterAttemptTaskEvent.attributes().getRequired(EventAttributes.TASK_ATTEMPT));
+    Assertions.assertEquals(
+        true, afterAttemptTaskEvent.attributes().getRequired(EventAttributes.TASK_SUCCESS));
   }
 }
diff --git a/runtime/service/src/testFixtures/java/org/apache/polaris/service/events/listeners/TestPolarisEventListener.java b/runtime/service/src/testFixtures/java/org/apache/polaris/service/events/listeners/TestPolarisEventListener.java
index 643dfa375..e23a7a926 100644
--- a/runtime/service/src/testFixtures/java/org/apache/polaris/service/events/listeners/TestPolarisEventListener.java
+++ b/runtime/service/src/testFixtures/java/org/apache/polaris/service/events/listeners/TestPolarisEventListener.java
@@ -20,830 +20,27 @@ package org.apache.polaris.service.events.listeners;
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import org.apache.polaris.service.events.AfterAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeAttemptTaskEvent;
-import org.apache.polaris.service.events.BeforeLimitRequestRateEvent;
-import org.apache.polaris.service.events.CatalogGenericTableServiceEvents;
-import org.apache.polaris.service.events.CatalogPolicyServiceEvents;
-import org.apache.polaris.service.events.CatalogsServiceEvents;
-import org.apache.polaris.service.events.IcebergRestCatalogEvents;
 import org.apache.polaris.service.events.PolarisEvent;
-import org.apache.polaris.service.events.PrincipalRolesServiceEvents;
-import org.apache.polaris.service.events.PrincipalsServiceEvents;
+import org.apache.polaris.service.events.PolarisEventType;
 
-/** Test event listener that stores all emitted events forever. */
+/** Test event listener that stores all emitted events by type. */
 public class TestPolarisEventListener implements PolarisEventListener {
-  private final Map<Class<? extends PolarisEvent>, PolarisEvent> latestEvents =
-      new ConcurrentHashMap<>();
+  private final Map<PolarisEventType, PolarisEvent> latestEvents = new ConcurrentHashMap<>();
 
-  private void recordEvent(PolarisEvent event) {
-    latestEvents.put(event.getClass(), event);
+  @Override
+  public void onEvent(PolarisEvent event) {
+    latestEvents.put(event.type(), event);
   }
 
   public void clear() {
     latestEvents.clear();
   }
 
-  public <T> T getLatest(Class<T> type) {
+  public PolarisEvent getLatest(PolarisEventType type) {
     var latest = latestEvents.get(type);
     if (latest == null) {
       throw new IllegalStateException("No event of type " + type + " recorded");
     }
-    return type.cast(latest);
-  }
-
-  @Override
-  public void onBeforeCreateCatalog(CatalogsServiceEvents.BeforeCreateCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateCatalog(CatalogsServiceEvents.AfterCreateCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDeleteCatalog(CatalogsServiceEvents.BeforeDeleteCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDeleteCatalog(CatalogsServiceEvents.AfterDeleteCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetCatalog(CatalogsServiceEvents.BeforeGetCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetCatalog(CatalogsServiceEvents.AfterGetCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdateCatalog(CatalogsServiceEvents.BeforeUpdateCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdateCatalog(CatalogsServiceEvents.AfterUpdateCatalogEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListCatalogs(CatalogsServiceEvents.BeforeListCatalogsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListCatalogs(CatalogsServiceEvents.AfterListCatalogsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreatePrincipal(PrincipalsServiceEvents.BeforeCreatePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreatePrincipal(PrincipalsServiceEvents.AfterCreatePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDeletePrincipal(PrincipalsServiceEvents.BeforeDeletePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDeletePrincipal(PrincipalsServiceEvents.AfterDeletePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetPrincipal(PrincipalsServiceEvents.BeforeGetPrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetPrincipal(PrincipalsServiceEvents.AfterGetPrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdatePrincipal(PrincipalsServiceEvents.BeforeUpdatePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdatePrincipal(PrincipalsServiceEvents.AfterUpdatePrincipalEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRotateCredentials(
-      PrincipalsServiceEvents.BeforeRotateCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRotateCredentials(PrincipalsServiceEvents.AfterRotateCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListPrincipals(PrincipalsServiceEvents.BeforeListPrincipalsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListPrincipals(PrincipalsServiceEvents.AfterListPrincipalsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeResetCredentials(PrincipalsServiceEvents.BeforeResetCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterResetCredentials(PrincipalsServiceEvents.AfterResetCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeAssignPrincipalRole(
-      PrincipalsServiceEvents.BeforeAssignPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterAssignPrincipalRole(
-      PrincipalsServiceEvents.AfterAssignPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRevokePrincipalRole(
-      PrincipalsServiceEvents.BeforeRevokePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRevokePrincipalRole(
-      PrincipalsServiceEvents.AfterRevokePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListAssignedPrincipalRoles(
-      PrincipalsServiceEvents.BeforeListAssignedPrincipalRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListAssignedPrincipalRoles(
-      PrincipalsServiceEvents.AfterListAssignedPrincipalRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreatePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeCreatePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreatePrincipalRole(
-      PrincipalRolesServiceEvents.AfterCreatePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDeletePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeDeletePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDeletePrincipalRole(
-      PrincipalRolesServiceEvents.AfterDeletePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeGetPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetPrincipalRole(
-      PrincipalRolesServiceEvents.AfterGetPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdatePrincipalRole(
-      PrincipalRolesServiceEvents.BeforeUpdatePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdatePrincipalRole(
-      PrincipalRolesServiceEvents.AfterUpdatePrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListPrincipalRoles(
-      PrincipalRolesServiceEvents.BeforeListPrincipalRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListPrincipalRoles(
-      PrincipalRolesServiceEvents.AfterListPrincipalRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreateCatalogRole(CatalogsServiceEvents.BeforeCreateCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateCatalogRole(CatalogsServiceEvents.AfterCreateCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDeleteCatalogRole(CatalogsServiceEvents.BeforeDeleteCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDeleteCatalogRole(CatalogsServiceEvents.AfterDeleteCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetCatalogRole(CatalogsServiceEvents.BeforeGetCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetCatalogRole(CatalogsServiceEvents.AfterGetCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdateCatalogRole(CatalogsServiceEvents.BeforeUpdateCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdateCatalogRole(CatalogsServiceEvents.AfterUpdateCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListCatalogRoles(CatalogsServiceEvents.BeforeListCatalogRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListCatalogRoles(CatalogsServiceEvents.AfterListCatalogRolesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeAssignCatalogRoleToPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeAssignCatalogRoleToPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterAssignCatalogRoleToPrincipalRole(
-      PrincipalRolesServiceEvents.AfterAssignCatalogRoleToPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRevokeCatalogRoleFromPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeRevokeCatalogRoleFromPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRevokeCatalogRoleFromPrincipalRole(
-      PrincipalRolesServiceEvents.AfterRevokeCatalogRoleFromPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListAssigneePrincipalsForPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeListAssigneePrincipalsForPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListAssigneePrincipalsForPrincipalRole(
-      PrincipalRolesServiceEvents.AfterListAssigneePrincipalsForPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListCatalogRolesForPrincipalRole(
-      PrincipalRolesServiceEvents.BeforeListCatalogRolesForPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListCatalogRolesForPrincipalRole(
-      PrincipalRolesServiceEvents.AfterListCatalogRolesForPrincipalRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeAddGrantToCatalogRole(
-      CatalogsServiceEvents.BeforeAddGrantToCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterAddGrantToCatalogRole(
-      CatalogsServiceEvents.AfterAddGrantToCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRevokeGrantFromCatalogRole(
-      CatalogsServiceEvents.BeforeRevokeGrantFromCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRevokeGrantFromCatalogRole(
-      CatalogsServiceEvents.AfterRevokeGrantFromCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListAssigneePrincipalRolesForCatalogRole(
-      CatalogsServiceEvents.BeforeListAssigneePrincipalRolesForCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListAssigneePrincipalRolesForCatalogRole(
-      CatalogsServiceEvents.AfterListAssigneePrincipalRolesForCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListGrantsForCatalogRole(
-      CatalogsServiceEvents.BeforeListGrantsForCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListGrantsForCatalogRole(
-      CatalogsServiceEvents.AfterListGrantsForCatalogRoleEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreateNamespace(IcebergRestCatalogEvents.BeforeCreateNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateNamespace(IcebergRestCatalogEvents.AfterCreateNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListNamespaces(IcebergRestCatalogEvents.BeforeListNamespacesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListNamespaces(IcebergRestCatalogEvents.AfterListNamespacesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadNamespaceMetadata(
-      IcebergRestCatalogEvents.BeforeLoadNamespaceMetadataEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadNamespaceMetadata(
-      IcebergRestCatalogEvents.AfterLoadNamespaceMetadataEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCheckExistsNamespace(
-      IcebergRestCatalogEvents.BeforeCheckExistsNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCheckExistsNamespace(
-      IcebergRestCatalogEvents.AfterCheckExistsNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDropNamespace(IcebergRestCatalogEvents.BeforeDropNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDropNamespace(IcebergRestCatalogEvents.AfterDropNamespaceEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdateNamespaceProperties(
-      IcebergRestCatalogEvents.BeforeUpdateNamespacePropertiesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdateNamespaceProperties(
-      IcebergRestCatalogEvents.AfterUpdateNamespacePropertiesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreateTable(IcebergRestCatalogEvents.BeforeCreateTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateTable(IcebergRestCatalogEvents.AfterCreateTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRefreshTable(IcebergRestCatalogEvents.BeforeRefreshTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRefreshTable(IcebergRestCatalogEvents.AfterRefreshTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListTables(IcebergRestCatalogEvents.BeforeListTablesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListTables(IcebergRestCatalogEvents.AfterListTablesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadTable(IcebergRestCatalogEvents.BeforeLoadTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadTable(IcebergRestCatalogEvents.AfterLoadTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCheckExistsTable(IcebergRestCatalogEvents.BeforeCheckExistsTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCheckExistsTable(IcebergRestCatalogEvents.AfterCheckExistsTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDropTable(IcebergRestCatalogEvents.BeforeDropTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDropTable(IcebergRestCatalogEvents.AfterDropTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRegisterTable(IcebergRestCatalogEvents.BeforeRegisterTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRegisterTable(IcebergRestCatalogEvents.AfterRegisterTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRenameTable(IcebergRestCatalogEvents.BeforeRenameTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRenameTable(IcebergRestCatalogEvents.AfterRenameTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdateTable(IcebergRestCatalogEvents.BeforeUpdateTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdateTable(IcebergRestCatalogEvents.AfterUpdateTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreateView(IcebergRestCatalogEvents.BeforeCreateViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateView(IcebergRestCatalogEvents.AfterCreateViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCommitView(IcebergRestCatalogEvents.BeforeCommitViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCommitView(IcebergRestCatalogEvents.AfterCommitViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRefreshView(IcebergRestCatalogEvents.BeforeRefreshViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRefreshView(IcebergRestCatalogEvents.AfterRefreshViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListViews(IcebergRestCatalogEvents.BeforeListViewsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListViews(IcebergRestCatalogEvents.AfterListViewsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadView(IcebergRestCatalogEvents.BeforeLoadViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadView(IcebergRestCatalogEvents.AfterLoadViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCheckExistsView(IcebergRestCatalogEvents.BeforeCheckExistsViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCheckExistsView(IcebergRestCatalogEvents.AfterCheckExistsViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDropView(IcebergRestCatalogEvents.BeforeDropViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDropView(IcebergRestCatalogEvents.AfterDropViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeRenameView(IcebergRestCatalogEvents.BeforeRenameViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterRenameView(IcebergRestCatalogEvents.AfterRenameViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeReplaceView(IcebergRestCatalogEvents.BeforeReplaceViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterReplaceView(IcebergRestCatalogEvents.AfterReplaceViewEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadCredentials(IcebergRestCatalogEvents.BeforeLoadCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadCredentials(IcebergRestCatalogEvents.AfterLoadCredentialsEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCommitTransaction(
-      IcebergRestCatalogEvents.BeforeCommitTransactionEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCommitTransaction(IcebergRestCatalogEvents.AfterCommitTransactionEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeSendNotification(IcebergRestCatalogEvents.BeforeSendNotificationEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterSendNotification(IcebergRestCatalogEvents.AfterSendNotificationEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetConfig(IcebergRestCatalogEvents.BeforeGetConfigEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetConfig(IcebergRestCatalogEvents.AfterGetConfigEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreatePolicy(CatalogPolicyServiceEvents.BeforeCreatePolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreatePolicy(CatalogPolicyServiceEvents.AfterCreatePolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListPolicies(CatalogPolicyServiceEvents.BeforeListPoliciesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListPolicies(CatalogPolicyServiceEvents.AfterListPoliciesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadPolicy(CatalogPolicyServiceEvents.BeforeLoadPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadPolicy(CatalogPolicyServiceEvents.AfterLoadPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeUpdatePolicy(CatalogPolicyServiceEvents.BeforeUpdatePolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterUpdatePolicy(CatalogPolicyServiceEvents.AfterUpdatePolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDropPolicy(CatalogPolicyServiceEvents.BeforeDropPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDropPolicy(CatalogPolicyServiceEvents.AfterDropPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeAttachPolicy(CatalogPolicyServiceEvents.BeforeAttachPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterAttachPolicy(CatalogPolicyServiceEvents.AfterAttachPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDetachPolicy(CatalogPolicyServiceEvents.BeforeDetachPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDetachPolicy(CatalogPolicyServiceEvents.AfterDetachPolicyEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeGetApplicablePolicies(
-      CatalogPolicyServiceEvents.BeforeGetApplicablePoliciesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterGetApplicablePolicies(
-      CatalogPolicyServiceEvents.AfterGetApplicablePoliciesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeCreateGenericTable(
-      CatalogGenericTableServiceEvents.BeforeCreateGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterCreateGenericTable(
-      CatalogGenericTableServiceEvents.AfterCreateGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeDropGenericTable(
-      CatalogGenericTableServiceEvents.BeforeDropGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterDropGenericTable(
-      CatalogGenericTableServiceEvents.AfterDropGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeListGenericTables(
-      CatalogGenericTableServiceEvents.BeforeListGenericTablesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterListGenericTables(
-      CatalogGenericTableServiceEvents.AfterListGenericTablesEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLoadGenericTable(
-      CatalogGenericTableServiceEvents.BeforeLoadGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterLoadGenericTable(
-      CatalogGenericTableServiceEvents.AfterLoadGenericTableEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeAttemptTask(BeforeAttemptTaskEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onAfterAttemptTask(AfterAttemptTaskEvent event) {
-    recordEvent(event);
-  }
-
-  @Override
-  public void onBeforeLimitRequestRate(BeforeLimitRequestRateEvent event) {
-    recordEvent(event);
+    return latest;
   }
 }