You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by su...@apache.org on 2026/03/12 15:24:36 UTC

(polaris) branch main updated: core: Standardize Path Lookup Key for `PolarisResolutionManifest.getResolvedPath` (#3940)

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

sungwy 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 6749238667 core: Standardize Path Lookup Key for `PolarisResolutionManifest.getResolvedPath` (#3940)
6749238667 is described below

commit 6749238667aa4b1967ee024948d73cccacdc43b1
Author: Sung Yun <10...@users.noreply.github.com>
AuthorDate: Thu Mar 12 11:24:28 2026 -0400

    core: Standardize Path Lookup Key for `PolarisResolutionManifest.getResolvedPath` (#3940)
    
    * use standardized path key in PolarisResolutionManifest
    
    * comment
    
    * adopt feedback
    
    * Rename PolarisResolutionManifestLookUpKeyTest.java to PolarisResolutionManifestLookupKeyTest.java
    
    * adopt nits. thanks alex and dmitri!
    
    * more nits
---
 .../resolver/PolarisResolutionManifest.java        | 108 +++++-----------
 .../PolarisResolutionManifestCatalogView.java      |  10 +-
 .../core/persistence/resolver/ResolvedPathKey.java |  60 +++++++++
 .../core/persistence/resolver/ResolverPath.java    |  40 ++++--
 .../PolarisResolutionManifestLookupKeyTest.java    | 139 +++++++++++++++++++++
 .../polaris/service/admin/PolarisAdminService.java |  88 +++++++------
 .../service/catalog/common/CatalogHandler.java     |  62 ++++-----
 .../service/catalog/common/CatalogUtils.java       |   7 +-
 .../generic/PolarisGenericTableCatalog.java        |  13 +-
 .../service/catalog/iceberg/IcebergCatalog.java    | 105 ++++++++++------
 .../catalog/iceberg/IcebergCatalogHandler.java     |   9 +-
 .../service/catalog/policy/PolicyCatalog.java      |  18 ++-
 .../catalog/policy/PolicyCatalogHandler.java       |  20 +--
 .../service/catalog/policy/PolicyCatalogUtils.java |   7 +-
 .../service/admin/PolarisAdminServiceTest.java     | 130 ++++++++++++++-----
 .../catalog/PolarisPassthroughResolutionView.java  | 108 +++++-----------
 16 files changed, 578 insertions(+), 346 deletions(-)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
index 4954cdd482..be9e755ff7 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
@@ -57,11 +57,11 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
   private final Resolver primaryResolver;
   private final PolarisDiagnostics diagnostics;
 
-  private final Map<Object, Integer> pathLookup = new HashMap<>();
+  private final Map<ResolvedPathKey, Integer> pathLookup = new HashMap<>();
   private final List<ResolverPath> addedPaths = new ArrayList<>();
   private final Multimap<String, PolarisEntityType> addedTopLevelNames = HashMultimap.create();
 
-  private final Map<Object, ResolverPath> passthroughPaths = new HashMap<>();
+  private final Map<ResolvedPathKey, ResolverPath> passthroughPaths = new HashMap<>();
 
   private int currentPathIndex = 0;
 
@@ -112,29 +112,20 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     }
   }
 
-  /**
-   * Adds a path that will be statically resolved with the primary Resolver when resolveAll() is
-   * called, and which contributes to the resolution status of whether all paths have successfully
-   * resolved.
-   *
-   * @param key the friendly lookup key for retrieving resolvedPaths after resolveAll(); typically
-   *     might be a Namespace or TableIdentifier object.
-   */
-  public void addPath(ResolverPath path, Object key) {
+  /** Adds a statically resolved path using canonical registration semantics only. */
+  public void addPath(ResolverPath path) {
     primaryResolver.addPath(path);
-    pathLookup.put(key, currentPathIndex);
+    // Preserve prior manifest lookup behavior: re-registering the same lookup key overwrites the
+    // previous mapping (last-write-wins).
+    pathLookup.put(ResolvedPathKey.of(path), currentPathIndex);
     addedPaths.add(path);
     ++currentPathIndex;
   }
 
-  /**
-   * Adds a path that is allowed to be dynamically resolved with a new Resolver when
-   * getPassthroughResolvedPath is called. These paths are also included in the primary static
-   * resolution set resolved during resolveAll().
-   */
-  public void addPassthroughPath(ResolverPath path, Object key) {
-    addPath(path, key);
-    passthroughPaths.put(key, path);
+  /** Adds a passthrough path using canonical registration semantics only. */
+  public void addPassthroughPath(ResolverPath path) {
+    addPath(path);
+    passthroughPaths.put(ResolvedPathKey.of(path), path);
   }
 
   public ResolverStatus resolveAll() {
@@ -174,40 +165,26 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     return getResolvedReferenceCatalogEntity(false);
   }
 
-  /**
-   * @param key the key associated with the path to retrieve that was specified in addPath
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional"
-   */
   @Override
-  public PolarisResolvedPathWrapper getResolvedPath(Object key) {
+  public PolarisResolvedPathWrapper getResolvedPath(ResolvedPathKey key) {
     return getResolvedPath(key, false);
   }
 
-  /**
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional", or if it was resolved but the subType doesn't match the specified subType.
-   */
   @Override
   public PolarisResolvedPathWrapper getResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType) {
-    return getResolvedPath(key, entityType, subType, false);
+      ResolvedPathKey key, PolarisEntitySubType subType) {
+    return getResolvedPath(key, subType, false);
   }
 
-  /**
-   * @param key the key associated with the path to retrieve that was specified in addPath
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional"
-   */
   @Override
-  public PolarisResolvedPathWrapper getPassthroughResolvedPath(Object key) {
+  public PolarisResolvedPathWrapper getPassthroughResolvedPath(ResolvedPathKey key) {
+    ResolverPath requestedPath = passthroughPaths.get(key);
     diagnostics.check(
-        passthroughPaths.containsKey(key),
-        "invalid_key_for_passthrough_resolved_path",
+        requestedPath != null,
+        "invalid_path_key_for_passthrough_resolved_path",
         "key={} passthroughPaths={}",
         key,
         passthroughPaths);
-    ResolverPath requestedPath = passthroughPaths.get(key);
 
     // Run a single-use Resolver for this path.
     Resolver passthroughResolver = resolverFactory.createResolver(principal, catalogName);
@@ -215,22 +192,19 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     ResolverStatus status = passthroughResolver.resolveAll();
 
     if (status.getStatus() != ResolverStatus.StatusEnum.SUCCESS) {
-      LOGGER.debug("Returning null for key {} due to resolver status {}", key, status.getStatus());
+      LOGGER.debug(
+          "Returning null for path {} due to resolver status {}",
+          requestedPath,
+          status.getStatus());
       return null;
     }
 
     List<ResolvedPolarisEntity> resolvedPath = passthroughResolver.getResolvedPath();
-    // If the catalog is a passthrough facade, we can go ahead and just return only as much of
-    // the parent path as was successfully found.
-    // TODO: For passthrough facade semantics, consider whether this should be where we generate
-    // the JIT-created entities that would get committed after we find them in the remote
-    // catalog.
     if (requestedPath.optional() && !getIsPassthroughFacade()) {
       if (resolvedPath.size() != requestedPath.entityNames().size()) {
         LOGGER.debug(
-            "Returning null for key {} due to size mismatch from getPassthroughResolvedPath "
+            "Returning null due to size mismatch from getPassthroughResolvedPath "
                 + "resolvedPath: {}, requestedPath.getEntityNames(): {}",
-            key,
             resolvedPath,
             requestedPath.entityNames());
         return null;
@@ -245,13 +219,9 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     return new PolarisResolvedPathWrapper(resolvedEntities);
   }
 
-  /**
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional", or if it was resolved but the subType doesn't match the specified subType.
-   */
   @Override
   public PolarisResolvedPathWrapper getPassthroughResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType) {
+      ResolvedPathKey key, PolarisEntitySubType subType) {
     PolarisResolvedPathWrapper resolvedPath = getPassthroughResolvedPath(key);
     if (resolvedPath == null) {
       return null;
@@ -327,7 +297,7 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     }
   }
 
-  public PolarisEntitySubType getLeafSubType(Object key) {
+  public PolarisEntitySubType getLeafSubType(ResolvedPathKey key) {
     diagnostics.check(
         pathLookup.containsKey(key),
         "never_registered_key_for_resolved_path",
@@ -342,25 +312,19 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     return resolved.get(resolved.size() - 1).getEntity().getSubType();
   }
 
-  /**
-   * @param key the key associated with the path to retrieve that was specified in addPath
-   * @param prependRootContainer if true, also includes the rootContainer as the first element of
-   *     the path; otherwise, the first element begins with the referenceCatalog.
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional"
-   */
-  public PolarisResolvedPathWrapper getResolvedPath(Object key, boolean prependRootContainer) {
+  /** Resolves a registered path using a canonical lookup key. */
+  public PolarisResolvedPathWrapper getResolvedPath(
+      ResolvedPathKey key, boolean prependRootContainer) {
     diagnostics.check(
         pathLookup.containsKey(key),
         "never_registered_key_for_resolved_path",
         "key={} pathLookup={}",
         key,
         pathLookup);
-
+    int index = pathLookup.get(key);
     if (!isResolveAllSucceeded()) {
       return null;
     }
-    int index = pathLookup.get(key);
 
     // Return null for a partially-resolved "optional" path.
     ResolverPath requestedPath = addedPaths.get(index);
@@ -385,24 +349,12 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     return new PolarisResolvedPathWrapper(resolvedEntities);
   }
 
-  /**
-   * @return null if the path resolved for {@code key} isn't fully-resolved when specified as
-   *     "optional", or if it was resolved but the subType doesn't match the specified subType.
-   */
   public PolarisResolvedPathWrapper getResolvedPath(
-      Object key,
-      PolarisEntityType entityType,
-      PolarisEntitySubType subType,
-      boolean prependRootContainer) {
+      ResolvedPathKey key, PolarisEntitySubType subType, boolean prependRootContainer) {
     PolarisResolvedPathWrapper resolvedPath = getResolvedPath(key, prependRootContainer);
     if (resolvedPath == null) {
       return null;
     }
-    // In the case of a passthrough facade, we may have only resolved part of the parent path
-    // in which case the subtype wouldn't match; return the path anyways in this case.
-    //
-    // TODO: Reconcile how we'll handle "TABLE_NOT_FOUND" or "VIEW_NOT_FOUND" semantics
-    // against the remote catalog.
     if (!getIsPassthroughFacade()
         && resolvedPath.getRawLeafEntity() != null
         && subType != PolarisEntitySubType.ANY_SUBTYPE
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifestCatalogView.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifestCatalogView.java
index 5a8855925e..f4cc21ae89 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifestCatalogView.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifestCatalogView.java
@@ -22,7 +22,6 @@ import jakarta.annotation.Nullable;
 import java.util.Optional;
 import org.apache.polaris.core.entity.CatalogEntity;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
-import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 
 /**
@@ -39,13 +38,12 @@ public interface PolarisResolutionManifestCatalogView {
         .orElse(null);
   }
 
-  PolarisResolvedPathWrapper getResolvedPath(Object key);
+  PolarisResolvedPathWrapper getResolvedPath(ResolvedPathKey key);
 
-  PolarisResolvedPathWrapper getResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType);
+  PolarisResolvedPathWrapper getResolvedPath(ResolvedPathKey key, PolarisEntitySubType subType);
 
-  PolarisResolvedPathWrapper getPassthroughResolvedPath(Object key);
+  PolarisResolvedPathWrapper getPassthroughResolvedPath(ResolvedPathKey key);
 
   PolarisResolvedPathWrapper getPassthroughResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType);
+      ResolvedPathKey key, PolarisEntitySubType subType);
 }
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolvedPathKey.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolvedPathKey.java
new file mode 100644
index 0000000000..d1acfc2ca4
--- /dev/null
+++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolvedPathKey.java
@@ -0,0 +1,60 @@
+/*
+ * 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.core.persistence.resolver;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
+import org.apache.polaris.core.entity.PolarisEntityType;
+
+/** Canonical lookup key for resolved paths in a {@link PolarisResolutionManifest}. */
+public record ResolvedPathKey(List<String> entityNames, PolarisEntityType entityType) {
+
+  public ResolvedPathKey {
+    entityNames = List.copyOf(entityNames);
+  }
+
+  public static ResolvedPathKey of(List<String> entityNames, PolarisEntityType entityType) {
+    return new ResolvedPathKey(entityNames, entityType);
+  }
+
+  public static ResolvedPathKey of(ResolverPath path) {
+    return path.key();
+  }
+
+  public static ResolvedPathKey ofNamespace(Namespace namespace) {
+    return new ResolvedPathKey(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE);
+  }
+
+  public static ResolvedPathKey ofTableLike(TableIdentifier identifier) {
+    return new ResolvedPathKey(
+        PolarisCatalogHelpers.tableIdentifierToList(identifier), PolarisEntityType.TABLE_LIKE);
+  }
+
+  public static ResolvedPathKey ofPolicy(Namespace namespace, String name) {
+    return new ResolvedPathKey(
+        PolarisCatalogHelpers.identifierToList(namespace, name), PolarisEntityType.POLICY);
+  }
+
+  public static ResolvedPathKey ofCatalogRole(String roleName) {
+    return new ResolvedPathKey(List.of(roleName), PolarisEntityType.CATALOG_ROLE);
+  }
+}
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolverPath.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolverPath.java
index c76bf04d07..43cca6e0bb 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolverPath.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/ResolverPath.java
@@ -24,14 +24,18 @@ import org.apache.polaris.core.entity.PolarisEntityType;
 /**
  * Simple class to represent a path within a catalog
  *
- * @param entityNames name of the entities in that path. The parent of the first named entity is the
- *     path is the catalog
- * @param lastEntityType all entities in a path are namespaces except the last one which can be a
- *     table_like entity versus a namespace
+ * @param key canonical path key (entity names + terminal entity type)
  * @param optional true if this path is optional, i.e. failing to fully resolve it is not an error
  */
-public record ResolverPath(
-    List<String> entityNames, PolarisEntityType lastEntityType, boolean optional) {
+public record ResolverPath(ResolvedPathKey key, boolean optional) {
+  /**
+   * Constructor for a non-optional path from a pre-built canonical key.
+   *
+   * @param key canonical path key (entity names + terminal entity type)
+   */
+  public ResolverPath(ResolvedPathKey key) {
+    this(key, false);
+  }
 
   /**
    * Constructor for a non-optional path.
@@ -40,6 +44,28 @@ public record ResolverPath(
    * @param lastEntityType type of the last entity
    */
   public ResolverPath(List<String> entityNames, PolarisEntityType lastEntityType) {
-    this(entityNames, lastEntityType, false);
+    this(new ResolvedPathKey(entityNames, lastEntityType), false);
+  }
+
+  /**
+   * Constructor for a path from entity names + terminal type.
+   *
+   * @param entityNames set of entity names, all are namespaces except the last one
+   * @param lastEntityType type of the last entity
+   * @param optional true if this path is optional, i.e. failing to fully resolve it is not an error
+   */
+  public ResolverPath(
+      List<String> entityNames, PolarisEntityType lastEntityType, boolean optional) {
+    this(new ResolvedPathKey(entityNames, lastEntityType), optional);
+  }
+
+  /** Compatibility accessor for existing call sites. */
+  public List<String> entityNames() {
+    return key.entityNames();
+  }
+
+  /** Compatibility accessor for existing call sites. */
+  public PolarisEntityType lastEntityType() {
+    return key.entityType();
   }
 }
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisResolutionManifestLookupKeyTest.java b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisResolutionManifestLookupKeyTest.java
new file mode 100644
index 0000000000..c880d9e4df
--- /dev/null
+++ b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisResolutionManifestLookupKeyTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.core.persistence;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
+import org.apache.polaris.core.persistence.resolver.Resolver;
+import org.apache.polaris.core.persistence.resolver.ResolverFactory;
+import org.apache.polaris.core.persistence.resolver.ResolverPath;
+import org.apache.polaris.core.persistence.resolver.ResolverStatus;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class PolarisResolutionManifestLookupKeyTest {
+
+  @Test
+  void pathKeyLookupResolvesRegisteredPath() {
+    Resolver resolver = Mockito.mock(Resolver.class);
+    ResolverFactory resolverFactory = Mockito.mock(ResolverFactory.class);
+    RealmContext realmContext = Mockito.mock(RealmContext.class);
+    when(resolverFactory.createResolver(any(), anyString())).thenReturn(resolver);
+    when(resolver.resolveAll()).thenReturn(new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS));
+    when(resolver.getIsPassthroughFacade()).thenReturn(false);
+
+    ResolvedPolarisEntity referenceCatalog = Mockito.mock(ResolvedPolarisEntity.class);
+    ResolvedPolarisEntity resolvedLeaf = Mockito.mock(ResolvedPolarisEntity.class);
+    when(resolver.getResolvedReferenceCatalog()).thenReturn(referenceCatalog);
+    when(resolver.getResolvedPaths()).thenReturn(List.of(List.of(resolvedLeaf)));
+
+    PolarisResolutionManifest manifest =
+        new PolarisResolutionManifest(
+            new PolarisDefaultDiagServiceImpl(),
+            realmContext,
+            resolverFactory,
+            PolarisPrincipal.of("p", Map.of(), Set.of()),
+            "catalog");
+
+    ResolverPath tablePath = new ResolverPath(List.of("ns1", "tbl1"), PolarisEntityType.TABLE_LIKE);
+    manifest.addPath(tablePath);
+    manifest.resolveAll();
+
+    var byKey = manifest.getResolvedPath(ResolvedPathKey.of(tablePath), false);
+    assertThat(byKey.getResolvedFullPath()).containsExactly(referenceCatalog, resolvedLeaf);
+  }
+
+  @Test
+  void pathKeyLookupFailsForUnregisteredPath() {
+    Resolver resolver = Mockito.mock(Resolver.class);
+    ResolverFactory resolverFactory = Mockito.mock(ResolverFactory.class);
+    RealmContext realmContext = Mockito.mock(RealmContext.class);
+    when(resolverFactory.createResolver(any(), anyString())).thenReturn(resolver);
+    when(resolver.resolveAll()).thenReturn(new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS));
+    when(resolver.getIsPassthroughFacade()).thenReturn(false);
+    when(resolver.getResolvedReferenceCatalog())
+        .thenReturn(Mockito.mock(ResolvedPolarisEntity.class));
+    when(resolver.getResolvedPaths()).thenReturn(List.of());
+
+    PolarisResolutionManifest manifest =
+        new PolarisResolutionManifest(
+            new PolarisDefaultDiagServiceImpl(),
+            realmContext,
+            resolverFactory,
+            PolarisPrincipal.of("p", Map.of(), Set.of()),
+            "catalog");
+    manifest.resolveAll();
+
+    assertThatThrownBy(
+            () ->
+                manifest.getResolvedPath(
+                    ResolvedPathKey.of(List.of("does_not_exist"), PolarisEntityType.NAMESPACE),
+                    false))
+        .isInstanceOf(IllegalStateException.class)
+        .hasMessageContaining("never_registered_key_for_resolved_path");
+  }
+
+  @Test
+  void pathKeyLookupUsesLastRegistrationForDuplicateCanonicalPath() {
+    Resolver resolver = Mockito.mock(Resolver.class);
+    ResolverFactory resolverFactory = Mockito.mock(ResolverFactory.class);
+    RealmContext realmContext = Mockito.mock(RealmContext.class);
+    when(resolverFactory.createResolver(any(), anyString())).thenReturn(resolver);
+    when(resolver.resolveAll()).thenReturn(new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS));
+    when(resolver.getIsPassthroughFacade()).thenReturn(false);
+
+    ResolvedPolarisEntity resolvedLeaf = Mockito.mock(ResolvedPolarisEntity.class);
+    when(resolver.getResolvedReferenceCatalog())
+        .thenReturn(Mockito.mock(ResolvedPolarisEntity.class));
+    when(resolver.getResolvedPaths())
+        .thenReturn(List.of(List.of(resolvedLeaf), List.of(resolvedLeaf)));
+
+    PolarisResolutionManifest manifest =
+        new PolarisResolutionManifest(
+            new PolarisDefaultDiagServiceImpl(),
+            realmContext,
+            resolverFactory,
+            PolarisPrincipal.of("p", Map.of(), Set.of()),
+            "catalog");
+
+    ResolverPath nonOptionalPath =
+        new ResolverPath(List.of("ns1", "tbl1"), PolarisEntityType.TABLE_LIKE, false);
+    ResolverPath optionalPath =
+        new ResolverPath(List.of("ns1", "tbl1"), PolarisEntityType.TABLE_LIKE, true);
+
+    manifest.addPath(nonOptionalPath);
+    manifest.addPath(optionalPath);
+    manifest.resolveAll();
+
+    assertThat(manifest.getResolvedPath(ResolvedPathKey.of(optionalPath), false)).isNull();
+  }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
index b47f4790a8..ddc9b8f2fb 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
@@ -112,6 +112,7 @@ import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
 import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverPath;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
 import org.apache.polaris.core.policy.PolicyEntity;
@@ -217,7 +218,8 @@ public class PolarisAdminService {
 
   private static CatalogRoleEntity getCatalogRoleByName(
       PolarisResolutionManifest resolutionManifest, String catalogRoleName) {
-    return Optional.ofNullable(resolutionManifest.getResolvedPath(catalogRoleName))
+    return Optional.ofNullable(
+            resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)))
         .map(PolarisResolvedPathWrapper::getRawLeafEntity)
         .map(CatalogRoleEntity::of)
         .orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
@@ -302,11 +304,10 @@ public class PolarisAdminService {
   private PolarisResolutionManifest authorizeBasicCatalogRoleOperationOrThrow(
       PolarisAuthorizableOperation op, String catalogName, String catalogRoleName) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     resolutionManifest.resolveAll();
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(catalogRoleName, true);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
     if (target == null) {
       throw new NotFoundException("CatalogRole does not exist: %s", catalogRoleName);
     }
@@ -383,9 +384,7 @@ public class PolarisAdminService {
       String catalogRoleName,
       String principalRoleName) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     resolutionManifest.addTopLevelName(
         principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
     ResolverStatus status = resolutionManifest.resolveAll();
@@ -404,7 +403,7 @@ public class PolarisAdminService {
         resolutionManifest.getResolvedTopLevelEntity(
             principalRoleName, PolarisEntityType.PRINCIPAL_ROLE);
     PolarisResolvedPathWrapper catalogRoleWrapper =
-        resolutionManifest.getResolvedPath(catalogRoleName, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
 
     authorizer.authorizeOrThrow(
         polarisPrincipal,
@@ -420,9 +419,7 @@ public class PolarisAdminService {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
     resolutionManifest.addTopLevelName(
         catalogName, PolarisEntityType.CATALOG, false /* isOptional */);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     ResolverStatus status = resolutionManifest.resolveAll();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
@@ -434,7 +431,7 @@ public class PolarisAdminService {
     PolarisResolvedPathWrapper catalogWrapper =
         resolutionManifest.getResolvedTopLevelEntity(catalogName, PolarisEntityType.CATALOG);
     PolarisResolvedPathWrapper catalogRoleWrapper =
-        resolutionManifest.getResolvedPath(catalogRoleName, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
     authorizer.authorizeOrThrow(
         polarisPrincipal,
         resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
@@ -451,11 +448,8 @@ public class PolarisAdminService {
       String catalogRoleName) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
     resolutionManifest.addPassthroughPath(
-        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE),
-        namespace);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     ResolverStatus status = resolutionManifest.resolveAll();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
@@ -470,9 +464,9 @@ public class PolarisAdminService {
     }
 
     PolarisResolvedPathWrapper namespaceWrapper =
-        resolutionManifest.getResolvedPath(namespace, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
     PolarisResolvedPathWrapper catalogRoleWrapper =
-        resolutionManifest.getResolvedPath(catalogRoleName, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
 
     authorizer.authorizeOrThrow(
         polarisPrincipal,
@@ -492,15 +486,11 @@ public class PolarisAdminService {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
     resolutionManifest.addPassthroughPath(
         new ResolverPath(
-            Arrays.asList(identifier.namespace().levels()), PolarisEntityType.NAMESPACE),
-        identifier.namespace());
+            Arrays.asList(identifier.namespace().levels()), PolarisEntityType.NAMESPACE));
     resolutionManifest.addPassthroughPath(
         new ResolverPath(
-            PolarisCatalogHelpers.tableIdentifierToList(identifier), PolarisEntityType.TABLE_LIKE),
-        identifier);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+            PolarisCatalogHelpers.tableIdentifierToList(identifier), PolarisEntityType.TABLE_LIKE));
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     ResolverStatus status = resolutionManifest.resolveAll();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
@@ -516,7 +506,7 @@ public class PolarisAdminService {
     CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
     PolarisResolvedPathWrapper tableLikeWrapper =
         resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE, true);
+            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE, true);
     boolean rbacForFederatedCatalogsEnabled =
         realmConfig.getConfig(
             FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS, catalogEntity);
@@ -526,7 +516,7 @@ public class PolarisAdminService {
     }
 
     PolarisResolvedPathWrapper catalogRoleWrapper =
-        resolutionManifest.getResolvedPath(catalogRoleName, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
 
     authorizer.authorizeOrThrow(
         polarisPrincipal,
@@ -546,11 +536,8 @@ public class PolarisAdminService {
     resolutionManifest.addPath(
         new ResolverPath(
             PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
-            PolarisEntityType.POLICY),
-        identifier);
-    resolutionManifest.addPath(
-        new ResolverPath(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE),
-        catalogRoleName);
+            PolarisEntityType.POLICY));
+    resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     ResolverStatus status = resolutionManifest.resolveAll();
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException("Catalog not found: %s", catalogName);
@@ -562,9 +549,11 @@ public class PolarisAdminService {
       }
     }
 
-    PolarisResolvedPathWrapper policyWrapper = resolutionManifest.getResolvedPath(identifier, true);
+    PolarisResolvedPathWrapper policyWrapper =
+        resolutionManifest.getResolvedPath(
+            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()), true);
     PolarisResolvedPathWrapper catalogRoleWrapper =
-        resolutionManifest.getResolvedPath(catalogRoleName, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
 
     authorizer.authorizeOrThrow(
         polarisPrincipal,
@@ -1363,7 +1352,8 @@ public class PolarisAdminService {
     PolarisResolutionManifest resolutionManifest =
         authorizeBasicCatalogRoleOperationOrThrow(op, catalogName, name);
 
-    PolarisResolvedPathWrapper resolvedCatalogRoleEntity = resolutionManifest.getResolvedPath(name);
+    PolarisResolvedPathWrapper resolvedCatalogRoleEntity =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(name));
     if (resolvedCatalogRoleEntity == null) {
       throw new NotFoundException("CatalogRole %s not found in catalog %s", name, catalogName);
     }
@@ -1692,7 +1682,8 @@ public class PolarisAdminService {
     CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
     CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
 
-    PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedPathWrapper =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedPathWrapper == null
         || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
       boolean rbacForFederatedCatalogsEnabled =
@@ -1735,7 +1726,8 @@ public class PolarisAdminService {
 
     CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
 
-    PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedPathWrapper =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedPathWrapper == null
         || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
       throw new NotFoundException("Namespace %s not found", namespace);
@@ -1808,7 +1800,7 @@ public class PolarisAdminService {
         syntheticNamespace = PolarisEntity.of(result.getEntity());
       } else if (result.getReturnStatus() == BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS) {
         PolarisResolvedPathWrapper partialPath =
-            resolutionManifest.getPassthroughResolvedPath(namespace);
+            resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(namespace));
         PolarisEntity partialLeafEntity =
             partialPath != null ? partialPath.getRawLeafEntity() : null;
         if (partialLeafEntity == null
@@ -1830,7 +1822,7 @@ public class PolarisAdminService {
       currentParent = syntheticNamespace;
     }
     PolarisResolvedPathWrapper resolvedPathWrapper =
-        resolutionManifest.getPassthroughResolvedPath(namespace);
+        resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     return resolvedPathWrapper;
   }
 
@@ -2131,7 +2123,7 @@ public class PolarisAdminService {
 
     PolarisResolvedPathWrapper resolvedPathWrapper =
         resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
+            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE);
     if (resolvedPathWrapper == null
         || !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
       boolean rbacForFederatedCatalogsEnabled =
@@ -2221,7 +2213,7 @@ public class PolarisAdminService {
         syntheticTableEntity);
 
     PolarisResolvedPathWrapper completePathWrapper =
-        resolutionManifest.getPassthroughResolvedPath(identifier);
+        resolutionManifest.getPassthroughResolvedPath(ResolvedPathKey.ofTableLike(identifier));
     PolarisEntity leafEntity =
         completePathWrapper != null ? completePathWrapper.getRawLeafEntity() : null;
     if (completePathWrapper == null
@@ -2252,7 +2244,7 @@ public class PolarisAdminService {
 
     PolarisResolvedPathWrapper resolvedPathWrapper =
         resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
+            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE);
     if (resolvedPathWrapper == null
         || !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
       throw notFoundExceptionForTableLikeEntity(identifier, subTypes);
@@ -2276,7 +2268,9 @@ public class PolarisAdminService {
     CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
     CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
 
-    PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(identifier);
+    PolarisResolvedPathWrapper resolvedPathWrapper =
+        resolutionManifest.getResolvedPath(
+            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()));
     if (resolvedPathWrapper == null) {
       throw new NoSuchPolicyException(String.format("Policy not exists: %s", identifier));
     }
@@ -2300,7 +2294,9 @@ public class PolarisAdminService {
     CatalogEntity catalogEntity = getCatalogByName(resolutionManifest, catalogName);
     CatalogRoleEntity catalogRoleEntity = getCatalogRoleByName(resolutionManifest, catalogRoleName);
 
-    PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(identifier);
+    PolarisResolvedPathWrapper resolvedPathWrapper =
+        resolutionManifest.getResolvedPath(
+            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()));
     if (resolvedPathWrapper == null) {
       throw new NoSuchPolicyException(String.format("Policy not exists: %s", identifier));
     }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
index b6fba5c853..5605cab033 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
@@ -42,6 +42,7 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverPath;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
 import org.apache.polaris.service.types.PolicyIdentifier;
@@ -100,15 +101,13 @@ public abstract class CatalogHandler {
       List<PolicyIdentifier> extraPassThroughPolicies) {
     resolutionManifest = newResolutionManifest();
     resolutionManifest.addPath(
-        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE),
-        namespace);
+        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
 
     if (extraPassthroughNamespaces != null) {
       for (Namespace ns : extraPassthroughNamespaces) {
         resolutionManifest.addPassthroughPath(
             new ResolverPath(
-                Arrays.asList(ns.levels()), PolarisEntityType.NAMESPACE, true /* optional */),
-            ns);
+                Arrays.asList(ns.levels()), PolarisEntityType.NAMESPACE, true /* optional */));
       }
     }
     if (extraPassthroughTableLikes != null) {
@@ -117,8 +116,7 @@ public abstract class CatalogHandler {
             new ResolverPath(
                 PolarisCatalogHelpers.tableIdentifierToList(id),
                 PolarisEntityType.TABLE_LIKE,
-                true /* optional */),
-            id);
+                true /* optional */));
       }
     }
 
@@ -128,13 +126,13 @@ public abstract class CatalogHandler {
             new ResolverPath(
                 PolarisCatalogHelpers.identifierToList(id.namespace(), id.name()),
                 PolarisEntityType.POLICY,
-                true /* optional */),
-            id);
+                true /* optional */));
       }
     }
 
     resolutionManifest.resolveAll();
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(namespace, true);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
     if (target == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -155,8 +153,7 @@ public abstract class CatalogHandler {
 
     Namespace parentNamespace = PolarisCatalogHelpers.getParentNamespace(namespace);
     resolutionManifest.addPath(
-        new ResolverPath(Arrays.asList(parentNamespace.levels()), PolarisEntityType.NAMESPACE),
-        parentNamespace);
+        new ResolverPath(Arrays.asList(parentNamespace.levels()), PolarisEntityType.NAMESPACE));
 
     // When creating an entity under a namespace, the authz target is the parentNamespace, but we
     // must also add the actual path that will be created as an "optional" passthrough resolution
@@ -164,10 +161,10 @@ public abstract class CatalogHandler {
     // a conflicting entity.
     resolutionManifest.addPassthroughPath(
         new ResolverPath(
-            Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE, true /* optional */),
-        namespace);
+            Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE, true /* optional */));
     resolutionManifest.resolveAll();
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(parentNamespace, true);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(parentNamespace), true);
     if (target == null) {
       throw noSuchNamespaceException(parentNamespace);
     }
@@ -188,8 +185,7 @@ public abstract class CatalogHandler {
 
     resolutionManifest = newResolutionManifest();
     resolutionManifest.addPath(
-        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE),
-        namespace);
+        new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
 
     // When creating an entity under a namespace, the authz target is the namespace, but we must
     // also
@@ -201,10 +197,10 @@ public abstract class CatalogHandler {
         new ResolverPath(
             PolarisCatalogHelpers.tableIdentifierToList(identifier),
             PolarisEntityType.TABLE_LIKE,
-            true /* optional */),
-        identifier);
+            true /* optional */));
     resolutionManifest.resolveAll();
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(namespace, true);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
     if (target == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -233,8 +229,7 @@ public abstract class CatalogHandler {
           new ResolverPath(
               PolarisCatalogHelpers.tableIdentifierToList(identifier),
               PolarisEntityType.TABLE_LIKE,
-              true /* optional */),
-          identifier);
+              true /* optional */));
       resolutionManifest.resolveAll();
     }
   }
@@ -250,7 +245,7 @@ public abstract class CatalogHandler {
       TableIdentifier identifier) {
     ensureResolutionManifestForTable(identifier);
     PolarisResolvedPathWrapper target =
-        resolutionManifest.getResolvedPath(identifier, PolarisEntityType.TABLE_LIKE, subType, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(identifier), subType, true);
     if (target == null) {
       throw notFoundExceptionForTableLikeEntity(identifier, subType);
     }
@@ -278,8 +273,7 @@ public abstract class CatalogHandler {
             resolutionManifest.addPassthroughPath(
                 new ResolverPath(
                     PolarisCatalogHelpers.tableIdentifierToList(identifier),
-                    PolarisEntityType.TABLE_LIKE),
-                identifier));
+                    PolarisEntityType.TABLE_LIKE)));
 
     ResolverStatus status = resolutionManifest.resolveAll();
 
@@ -298,7 +292,7 @@ public abstract class CatalogHandler {
                 identifier ->
                     Optional.ofNullable(
                             resolutionManifest.getResolvedPath(
-                                identifier, PolarisEntityType.TABLE_LIKE, subType, true))
+                                ResolvedPathKey.ofTableLike(identifier), subType, true))
                         .orElseThrow(
                             () -> notFoundExceptionForTableLikeEntity(identifier, subType)))
             .toList();
@@ -322,22 +316,19 @@ public abstract class CatalogHandler {
     // Add src, dstParent, and dst(optional)
     resolutionManifest.addPath(
         new ResolverPath(
-            PolarisCatalogHelpers.tableIdentifierToList(src), PolarisEntityType.TABLE_LIKE),
-        src);
+            PolarisCatalogHelpers.tableIdentifierToList(src), PolarisEntityType.TABLE_LIKE));
     resolutionManifest.addPath(
-        new ResolverPath(Arrays.asList(dst.namespace().levels()), PolarisEntityType.NAMESPACE),
-        dst.namespace());
+        new ResolverPath(Arrays.asList(dst.namespace().levels()), PolarisEntityType.NAMESPACE));
     resolutionManifest.addPath(
         new ResolverPath(
             PolarisCatalogHelpers.tableIdentifierToList(dst),
             PolarisEntityType.TABLE_LIKE,
-            true /* optional */),
-        dst);
+            true /* optional */));
     ResolverStatus status = resolutionManifest.resolveAll();
     if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED
         && status.getFailedToResolvePath().lastEntityType() == PolarisEntityType.NAMESPACE) {
       throw noSuchNamespaceException(dst.namespace());
-    } else if (resolutionManifest.getResolvedPath(src, PolarisEntityType.TABLE_LIKE, subType)
+    } else if (resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(src), subType)
         == null) {
       throw notFoundExceptionForTableLikeEntity(dst, subType);
     }
@@ -349,7 +340,8 @@ public abstract class CatalogHandler {
     // type.
     // TODO: Possibly modify the exception thrown depending on whether the caller has privileges
     // on the parent namespace.
-    PolarisEntitySubType dstLeafSubType = resolutionManifest.getLeafSubType(dst);
+    PolarisEntitySubType dstLeafSubType =
+        resolutionManifest.getLeafSubType(ResolvedPathKey.ofTableLike(dst));
 
     switch (dstLeafSubType) {
       case ICEBERG_TABLE:
@@ -364,9 +356,9 @@ public abstract class CatalogHandler {
     }
 
     PolarisResolvedPathWrapper target =
-        resolutionManifest.getResolvedPath(src, PolarisEntityType.TABLE_LIKE, subType, true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(src), subType, true);
     PolarisResolvedPathWrapper secondary =
-        resolutionManifest.getResolvedPath(dst.namespace(), true);
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(dst.namespace()), true);
     authorizer()
         .authorizeOrThrow(
             polarisPrincipal(),
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogUtils.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogUtils.java
index ae76e1490c..0dcb1ba2b8 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogUtils.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogUtils.java
@@ -33,9 +33,9 @@ import org.apache.polaris.core.admin.model.StorageConfigInfo;
 import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.config.RealmConfig;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
-import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 
 /** Utility methods for working with Polaris catalog entities. */
@@ -52,11 +52,12 @@ public class CatalogUtils {
       PolarisResolutionManifestCatalogView resolvedEntityView, TableIdentifier tableIdentifier) {
     PolarisResolvedPathWrapper resolvedTableEntities =
         resolvedEntityView.getResolvedPath(
-            tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+            ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ICEBERG_TABLE);
     if (resolvedTableEntities != null) {
       return resolvedTableEntities;
     }
-    return resolvedEntityView.getResolvedPath(tableIdentifier.namespace());
+    return resolvedEntityView.getResolvedPath(
+        ResolvedPathKey.ofNamespace(tableIdentifier.namespace()));
   }
 
   /**
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalog.java
index a1c6078f43..b232f0e3a4 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalog.java
@@ -41,6 +41,7 @@ import org.apache.polaris.core.persistence.dao.entity.DropEntityResult;
 import org.apache.polaris.core.persistence.dao.entity.EntityResult;
 import org.apache.polaris.core.persistence.pagination.PageToken;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -79,7 +80,8 @@ public class PolarisGenericTableCatalog implements GenericTableCatalog {
       String doc,
       Map<String, String> properties) {
     PolarisResolvedPathWrapper resolvedParent =
-        resolvedEntityView.getResolvedPath(tableIdentifier.namespace());
+        resolvedEntityView.getResolvedPath(
+            ResolvedPathKey.ofNamespace(tableIdentifier.namespace()));
     if (resolvedParent == null) {
       // Illegal state because the namespace should've already been in the static resolution set.
       throw new IllegalStateException(
@@ -91,7 +93,7 @@ public class PolarisGenericTableCatalog implements GenericTableCatalog {
 
     PolarisResolvedPathWrapper resolvedEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
+            ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ANY_SUBTYPE);
     PolarisEntity entity = resolvedEntities == null ? null : resolvedEntities.getRawLeafEntity();
     if (null == entity) {
       entity =
@@ -137,7 +139,7 @@ public class PolarisGenericTableCatalog implements GenericTableCatalog {
   public GenericTableEntity loadGenericTable(TableIdentifier tableIdentifier) {
     PolarisResolvedPathWrapper resolvedEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.GENERIC_TABLE);
+            ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.GENERIC_TABLE);
     GenericTableEntity entity =
         GenericTableEntity.of(
             resolvedEntities == null ? null : resolvedEntities.getRawLeafEntity());
@@ -153,7 +155,7 @@ public class PolarisGenericTableCatalog implements GenericTableCatalog {
   public boolean dropGenericTable(TableIdentifier tableIdentifier) {
     PolarisResolvedPathWrapper resolvedEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.GENERIC_TABLE);
+            ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.GENERIC_TABLE);
 
     if (resolvedEntities == null) {
       throw notFoundExceptionForTableLikeEntity(
@@ -176,7 +178,8 @@ public class PolarisGenericTableCatalog implements GenericTableCatalog {
 
   @Override
   public List<TableIdentifier> listGenericTables(Namespace namespace) {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw noSuchNamespaceException(namespace);
     }
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 776a0a8b4a..5cd5490000 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
@@ -123,6 +123,7 @@ import org.apache.polaris.core.persistence.pagination.Page;
 import org.apache.polaris.core.persistence.pagination.PageToken;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverFactory;
 import org.apache.polaris.core.persistence.resolver.ResolverPath;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
@@ -315,7 +316,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     TableOperations ops = newTableOps(identifier);
 
     PolarisResolvedPathWrapper resolvedParent =
-        resolvedEntityView.getResolvedPath(identifier.namespace());
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(identifier.namespace()));
     if (resolvedParent == null) {
       // Illegal state because the namespace should've already been in the static resolution set.
       throw new IllegalStateException(
@@ -368,7 +369,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
           defaultNamespaceLocation(tableIdentifier.namespace()), tableIdentifier.name());
     } else {
       PolarisResolvedPathWrapper resolvedNamespace =
-          resolvedEntityView.getResolvedPath(tableIdentifier.namespace());
+          resolvedEntityView.getResolvedPath(
+              ResolvedPathKey.ofNamespace(tableIdentifier.namespace()));
       if (resolvedNamespace == null) {
         throw noSuchNamespaceException(tableIdentifier.namespace());
       }
@@ -496,7 +498,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     // TODO: These should really be helpers in core Iceberg Namespace.
     Namespace parentNamespace = PolarisCatalogHelpers.getParentNamespace(namespace);
 
-    PolarisResolvedPathWrapper resolvedParent = resolvedEntityView.getResolvedPath(parentNamespace);
+    PolarisResolvedPathWrapper resolvedParent =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(parentNamespace));
     if (resolvedParent == null) {
       throw new NoSuchNamespaceException(
           "Cannot create namespace %s. Parent namespace does not exist.", namespace);
@@ -630,9 +633,11 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   private PolarisResolvedPathWrapper getResolvedParentNamespace(Namespace namespace) {
     Namespace parentNamespace =
         Namespace.of(Arrays.copyOf(namespace.levels(), namespace.length() - 1));
-    PolarisResolvedPathWrapper resolvedParent = resolvedEntityView.getResolvedPath(parentNamespace);
+    PolarisResolvedPathWrapper resolvedParent =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(parentNamespace));
     if (resolvedParent == null) {
-      return resolvedEntityView.getPassthroughResolvedPath(parentNamespace);
+      return resolvedEntityView.getPassthroughResolvedPath(
+          ResolvedPathKey.ofNamespace(parentNamespace));
     }
     return resolvedParent;
   }
@@ -641,7 +646,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   public boolean namespaceExists(Namespace namespace) {
     return Optional.ofNullable(namespace)
         .filter(ns -> !ns.isEmpty())
-        .map(resolvedEntityView::getResolvedPath)
+        .map(ns -> resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(ns)))
         .isPresent();
   }
 
@@ -650,7 +655,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     if (namespace.isEmpty()) {
       throw new IllegalArgumentException("Cannot drop root namespace");
     }
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       return false;
     }
@@ -692,7 +698,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   @Override
   public boolean setProperties(Namespace namespace, Map<String, String> properties)
       throws NoSuchNamespaceException {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -738,7 +745,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   @Override
   public boolean removeProperties(Namespace namespace, Set<String> properties)
       throws NoSuchNamespaceException {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -770,7 +778,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   @Override
   public Map<String, String> loadNamespaceMetadata(Namespace namespace)
       throws NoSuchNamespaceException {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -796,7 +805,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
   public Page<Namespace> listNamespaces(Namespace namespace, PageToken pageToken)
       throws NoSuchNamespaceException {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw noSuchNamespaceException(namespace);
     }
@@ -1018,12 +1028,15 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
   private void validateLocationForTableLike(TableIdentifier identifier, String location) {
     PolarisResolvedPathWrapper resolvedStorageEntity =
         resolvedEntityView.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
+            ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ANY_SUBTYPE);
     if (resolvedStorageEntity == null) {
-      resolvedStorageEntity = resolvedEntityView.getResolvedPath(identifier.namespace());
+      resolvedStorageEntity =
+          resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(identifier.namespace()));
     }
     if (resolvedStorageEntity == null) {
-      resolvedStorageEntity = resolvedEntityView.getPassthroughResolvedPath(identifier.namespace());
+      resolvedStorageEntity =
+          resolvedEntityView.getPassthroughResolvedPath(
+              ResolvedPathKey.ofNamespace(identifier.namespace()));
     }
 
     validateLocationForTableLike(identifier, location, resolvedStorageEntity);
@@ -1248,12 +1261,12 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
         tbl ->
             resolutionManifest.addPath(
                 new ResolverPath(
-                    PolarisCatalogHelpers.tableIdentifierToList(tbl), PolarisEntityType.TABLE_LIKE),
-                tbl));
+                    PolarisCatalogHelpers.tableIdentifierToList(tbl),
+                    PolarisEntityType.TABLE_LIKE)));
     siblingNamespaces.forEach(
         ns ->
             resolutionManifest.addPath(
-                new ResolverPath(Arrays.asList(ns.levels()), PolarisEntityType.NAMESPACE), ns));
+                new ResolverPath(Arrays.asList(ns.levels()), PolarisEntityType.NAMESPACE)));
     ResolverStatus status = resolutionManifest.resolveAll();
     if (!status.getStatus().equals(ResolverStatus.StatusEnum.SUCCESS)) {
       String message =
@@ -1271,7 +1284,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
                 .map(
                     tbl -> {
                       PolarisResolvedPathWrapper resolveTablePath =
-                          resolutionManifest.getResolvedPath(tbl);
+                          resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(tbl));
                       PolarisEntity tableEntity = resolveTablePath.getRawLeafEntity();
                       if (tableEntity.getSubType() == PolarisEntitySubType.GENERIC_TABLE) {
                         return GenericTableEntity.of(tableEntity).getBaseLocation();
@@ -1283,7 +1296,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
                 .map(
                     ns -> {
                       PolarisResolvedPathWrapper resolveNamespacePath =
-                          resolutionManifest.getResolvedPath(ns);
+                          resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(ns));
                       return NamespaceEntity.of(resolveNamespacePath.getRawLeafEntity())
                           .getBaseLocation();
                     }))
@@ -1436,7 +1449,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       // table entity instead of the statically-resolved authz resolution set.
       PolarisResolvedPathWrapper resolvedEntities =
           resolvedEntityView.getPassthroughResolvedPath(
-              tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+              ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ICEBERG_TABLE);
       IcebergTableLikeEntity entity = null;
 
       if (resolvedEntities != null) {
@@ -1506,14 +1519,15 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
       PolarisResolvedPathWrapper resolvedTableEntities =
           resolvedEntityView.getPassthroughResolvedPath(
-              tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+              ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ICEBERG_TABLE);
 
       // Fetch credentials for the resolved entity. The entity could be the table itself (if it has
       // already been stored and credentials have been configured directly) or it could be the
       // table's namespace or catalog.
       PolarisResolvedPathWrapper resolvedStorageEntity =
           resolvedTableEntities == null
-              ? resolvedEntityView.getResolvedPath(tableIdentifier.namespace())
+              ? resolvedEntityView.getResolvedPath(
+                  ResolvedPathKey.ofNamespace(tableIdentifier.namespace()))
               : resolvedTableEntities;
 
       // refresh credentials because we need to read the metadata file to validate its location
@@ -1530,7 +1544,9 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
       List<PolarisEntity> resolvedNamespace =
           resolvedTableEntities == null
-              ? resolvedEntityView.getResolvedPath(tableIdentifier.namespace()).getRawFullPath()
+              ? resolvedEntityView
+                  .getResolvedPath(ResolvedPathKey.ofNamespace(tableIdentifier.namespace()))
+                  .getRawFullPath()
               : resolvedTableEntities.getRawParentPath();
 
       if (base == null
@@ -1572,7 +1588,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       // persistence-layer commit).
       PolarisResolvedPathWrapper resolvedPath =
           resolvedEntityView.getPassthroughResolvedPath(
-              tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
+              ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ANY_SUBTYPE);
       if (resolvedPath != null && resolvedPath.getRawLeafEntity() != null) {
         var subType = resolvedPath.getRawLeafEntity().getSubType();
         if (subType != PolarisEntitySubType.ICEBERG_TABLE) {
@@ -1856,7 +1872,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     public void doRefresh() {
       PolarisResolvedPathWrapper resolvedEntities =
           resolvedEntityView.getPassthroughResolvedPath(
-              identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_VIEW);
+              ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ICEBERG_VIEW);
       IcebergTableLikeEntity entity = null;
 
       if (resolvedEntities != null) {
@@ -1928,7 +1944,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
       PolarisResolvedPathWrapper resolvedTable =
           resolvedEntityView.getPassthroughResolvedPath(
-              identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+              ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ICEBERG_TABLE);
       if (resolvedTable != null) {
         throw alreadyExistsExceptionWithSameNameForTableLikeEntity(
             identifier, PolarisEntitySubType.ICEBERG_TABLE);
@@ -1936,19 +1952,22 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
       PolarisResolvedPathWrapper resolvedEntities =
           resolvedEntityView.getPassthroughResolvedPath(
-              identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_VIEW);
+              ResolvedPathKey.ofTableLike(identifier), PolarisEntitySubType.ICEBERG_VIEW);
 
       // Fetch credentials for the resolved entity. The entity could be the view itself (if it has
       // already been stored and credentials have been configured directly) or it could be the
       // table's namespace or catalog.
       PolarisResolvedPathWrapper resolvedStorageEntity =
           resolvedEntities == null
-              ? resolvedEntityView.getResolvedPath(identifier.namespace())
+              ? resolvedEntityView.getResolvedPath(
+                  ResolvedPathKey.ofNamespace(identifier.namespace()))
               : resolvedEntities;
 
       List<PolarisEntity> resolvedNamespace =
           resolvedEntities == null
-              ? resolvedEntityView.getResolvedPath(identifier.namespace()).getRawFullPath()
+              ? resolvedEntityView
+                  .getResolvedPath(ResolvedPathKey.ofNamespace(identifier.namespace()))
+                  .getRawFullPath()
               : resolvedEntities.getRawParentPath();
       if (base == null || !metadata.location().equals(base.location())) {
         // If location is changing then we must validate that the requested location is valid
@@ -2208,7 +2227,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       PolarisEntitySubType subType, TableIdentifier from, TableIdentifier to) {
     LOGGER.debug("Renaming tableLike from {} to {}", from, to);
     PolarisResolvedPathWrapper resolvedEntities =
-        resolvedEntityView.getResolvedPath(from, PolarisEntityType.TABLE_LIKE, subType);
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofTableLike(from), subType);
     if (resolvedEntities == null) {
       if (subType == PolarisEntitySubType.ICEBERG_VIEW) {
         throw new NoSuchViewException("Cannot rename %s to %s. View does not exist", from, to);
@@ -2222,7 +2241,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     List<PolarisEntity> newCatalogPath = null;
     if (!from.namespace().equals(to.namespace())) {
       PolarisResolvedPathWrapper resolvedNewParentEntities =
-          resolvedEntityView.getResolvedPath(to.namespace());
+          resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(to.namespace()));
       if (resolvedNewParentEntities == null) {
         throw new NoSuchNamespaceException(
             "Cannot rename %s to %s. Namespace does not exist: %s", from, to, to.namespace());
@@ -2315,7 +2334,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
    */
   private void createTableLike(TableIdentifier identifier, PolarisEntity entity) {
     PolarisResolvedPathWrapper resolvedParent =
-        resolvedEntityView.getResolvedPath(identifier.namespace());
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(identifier.namespace()));
     if (resolvedParent == null) {
       // Illegal state because the namespace should've already been in the static resolution set.
       throw new IllegalStateException(
@@ -2385,7 +2404,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
   private void updateTableLike(TableIdentifier identifier, PolarisEntity entity) {
     PolarisResolvedPathWrapper resolvedEntities =
-        resolvedEntityView.getResolvedPath(identifier, entity.getType(), entity.getSubType());
+        resolvedEntityView.getResolvedPath(
+            ResolvedPathKey.ofTableLike(identifier), entity.getSubType());
     if (resolvedEntities == null) {
       // Illegal state because the identifier should've already been in the static resolution set.
       throw new IllegalStateException(
@@ -2443,7 +2463,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       Map<String, String> storageProperties,
       boolean purge) {
     PolarisResolvedPathWrapper resolvedEntities =
-        resolvedEntityView.getResolvedPath(identifier, PolarisEntityType.TABLE_LIKE, subType);
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofTableLike(identifier), subType);
     if (resolvedEntities == null) {
       // TODO: Error?
       return new DropEntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
@@ -2482,7 +2502,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
         "Handling notification request {} for tableIdentifier {}", request, tableIdentifier);
     PolarisResolvedPathWrapper resolvedEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            tableIdentifier, PolarisEntityType.TABLE_LIKE, subType);
+            ResolvedPathKey.ofTableLike(tableIdentifier), subType);
 
     NotificationType notificationType = request.getNotificationType();
 
@@ -2507,7 +2527,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
                 Arrays.stream(tableIdentifier.namespace().levels())
                     .limit(i)
                     .toArray(String[]::new));
-        resolvedStorageEntity = resolvedEntityView.getResolvedPath(nsLevel);
+        resolvedStorageEntity =
+            resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(nsLevel));
         if (resolvedStorageEntity != null) {
           storageInfoEntity = FileIOUtil.findStorageInfoFromHierarchy(resolvedStorageEntity);
           break;
@@ -2543,7 +2564,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
       Namespace ns = tableIdentifier.namespace();
       createNonExistingNamespaces(ns);
 
-      PolarisResolvedPathWrapper resolvedParent = resolvedEntityView.getPassthroughResolvedPath(ns);
+      PolarisResolvedPathWrapper resolvedParent =
+          resolvedEntityView.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(ns));
 
       IcebergTableLikeEntity entity =
           IcebergTableLikeEntity.of(
@@ -2625,10 +2647,12 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     for (int i = 1; i <= namespace.length(); i++) {
       Namespace nsLevel =
           Namespace.of(Arrays.stream(namespace.levels()).limit(i).toArray(String[]::new));
-      if (resolvedEntityView.getPassthroughResolvedPath(nsLevel) == null) {
+      if (resolvedEntityView.getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(nsLevel))
+          == null) {
         Namespace parentNamespace = PolarisCatalogHelpers.getParentNamespace(nsLevel);
         PolarisResolvedPathWrapper resolvedParent =
-            resolvedEntityView.getPassthroughResolvedPath(parentNamespace);
+            resolvedEntityView.getPassthroughResolvedPath(
+                ResolvedPathKey.ofNamespace(parentNamespace));
         try {
           createNamespaceInternal(nsLevel, Collections.emptyMap(), resolvedParent);
         } catch (AlreadyExistsException aee) {
@@ -2652,7 +2676,8 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
 
   private Page<TableIdentifier> listTableLike(
       PolarisEntitySubType subType, Namespace namespace, PageToken pageToken) {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       // Illegal state because the namespace should've already been in the static resolution set.
       throw new IllegalStateException(
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
index 2d8664f204..dda26ff048 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
@@ -106,6 +106,7 @@ import org.apache.polaris.core.persistence.dao.entity.EntitiesResult;
 import org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
 import org.apache.polaris.core.persistence.pagination.Page;
 import org.apache.polaris.core.persistence.pagination.PageToken;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.Resolver;
 import org.apache.polaris.core.persistence.resolver.ResolverFactory;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
@@ -327,7 +328,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
           reservedProperties()
               .removeReservedProperties(
                   resolutionManifest
-                      .getPassthroughResolvedPath(namespace)
+                      .getPassthroughResolvedPath(ResolvedPathKey.ofNamespace(namespace))
                       .getRawLeafEntity()
                       .getPropertiesAsMap());
       return CreateNamespaceResponse.builder()
@@ -657,7 +658,8 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
     long catalogId = catalogEntity.getId();
 
     // Get the table ID from the resolved path
-    PolarisResolvedPathWrapper resolvedTable = resolutionManifest.getResolvedPath(identifier);
+    PolarisResolvedPathWrapper resolvedTable =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(identifier));
     PolarisEntity tableEntity = resolvedTable.getRawLeafEntity();
     long tableId = tableEntity.getId();
 
@@ -673,7 +675,8 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
    * @return the Polaris table entity for the table or null for external catalogs
    */
   private @Nullable IcebergTableLikeEntity getTableEntity(TableIdentifier tableIdentifier) {
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(tableIdentifier);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(tableIdentifier));
     PolarisEntity rawLeafEntity = target.getRawLeafEntity();
     if (rawLeafEntity.getType() == PolarisEntityType.TABLE_LIKE) {
       return IcebergTableLikeEntity.of(rawLeafEntity);
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java
index 147f550510..fdd1496ce4 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalog.java
@@ -54,6 +54,7 @@ import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
 import org.apache.polaris.core.persistence.dao.entity.LoadPolicyMappingsResult;
 import org.apache.polaris.core.persistence.pagination.PageToken;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.policy.PolicyEntity;
 import org.apache.polaris.core.policy.PolicyType;
 import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
@@ -91,7 +92,8 @@ public class PolicyCatalog {
   public Policy createPolicy(
       PolicyIdentifier policyIdentifier, String type, String description, String content) {
     PolarisResolvedPathWrapper resolvedParent =
-        resolvedEntityView.getResolvedPath(policyIdentifier.namespace());
+        resolvedEntityView.getResolvedPath(
+            ResolvedPathKey.ofNamespace(policyIdentifier.namespace()));
     if (resolvedParent == null) {
       // Illegal state because the namespace should've already been in the static resolution set.
       throw new IllegalStateException(
@@ -102,7 +104,8 @@ public class PolicyCatalog {
 
     PolarisResolvedPathWrapper resolvedPolicyEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            policyIdentifier, PolarisEntityType.POLICY, PolarisEntitySubType.NULL_SUBTYPE);
+            ResolvedPathKey.ofPolicy(policyIdentifier.namespace(), policyIdentifier.name()),
+            PolarisEntitySubType.NULL_SUBTYPE);
 
     PolicyEntity entity =
         PolicyEntity.of(
@@ -155,7 +158,8 @@ public class PolicyCatalog {
   }
 
   public List<PolicyIdentifier> listPolicies(Namespace namespace, @Nullable PolicyType policyType) {
-    PolarisResolvedPathWrapper resolvedEntities = resolvedEntityView.getResolvedPath(namespace);
+    PolarisResolvedPathWrapper resolvedEntities =
+        resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
     if (resolvedEntities == null) {
       throw new IllegalStateException(
           String.format("Failed to fetch resolved namespace '%s'", namespace));
@@ -446,7 +450,8 @@ public class PolicyCatalog {
       return List.of(catalogEntity);
     } else if (Strings.isNullOrEmpty(targetName)) {
       // namespace
-      var resolvedTargetEntity = resolvedEntityView.getResolvedPath(namespace);
+      var resolvedTargetEntity =
+          resolvedEntityView.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
       if (resolvedTargetEntity == null) {
         throw noSuchNamespaceException(namespace);
       }
@@ -457,7 +462,7 @@ public class PolicyCatalog {
       // only Iceberg tables are supported
       var resolvedTableEntity =
           resolvedEntityView.getResolvedPath(
-              tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+              ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ICEBERG_TABLE);
       if (resolvedTableEntity == null) {
         throw notFoundExceptionForTableLikeEntity(
             tableIdentifier, PolarisEntitySubType.ICEBERG_TABLE);
@@ -478,7 +483,8 @@ public class PolicyCatalog {
   private PolarisResolvedPathWrapper getResolvedPathWrapper(PolicyIdentifier policyIdentifier) {
     var resolvedEntities =
         resolvedEntityView.getPassthroughResolvedPath(
-            policyIdentifier, PolarisEntityType.POLICY, PolarisEntitySubType.NULL_SUBTYPE);
+            ResolvedPathKey.ofPolicy(policyIdentifier.namespace(), policyIdentifier.name()),
+            PolarisEntitySubType.NULL_SUBTYPE);
     if (resolvedEntities == null || resolvedEntities.getResolvedLeafEntity() == null) {
       throw new NoSuchPolicyException(String.format("Policy does not exist: %s", policyIdentifier));
     }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
index 1fe29ae414..a194e8e6c1 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
@@ -34,6 +34,7 @@ import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverPath;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
 import org.apache.polaris.core.policy.PolicyType;
@@ -142,11 +143,12 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
         new ResolverPath(
             PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
             PolarisEntityType.POLICY,
-            true /* optional */),
-        identifier);
+            true /* optional */));
     resolutionManifest.resolveAll();
 
-    PolarisResolvedPathWrapper target = resolutionManifest.getResolvedPath(identifier, true);
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(
+            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()), true);
     if (target == null) {
       throw new NoSuchPolicyException(String.format("Policy does not exist: %s", identifier));
     }
@@ -212,16 +214,14 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
         new ResolverPath(
             PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
             PolarisEntityType.POLICY,
-            true /* optional */),
-        identifier);
+            true /* optional */));
 
     switch (target.getType()) {
       case CATALOG -> {}
       case NAMESPACE -> {
         Namespace targetNamespace = Namespace.of(target.getPath().toArray(new String[0]));
         resolutionManifest.addPath(
-            new ResolverPath(Arrays.asList(targetNamespace.levels()), PolarisEntityType.NAMESPACE),
-            targetNamespace);
+            new ResolverPath(Arrays.asList(targetNamespace.levels()), PolarisEntityType.NAMESPACE));
       }
       case TABLE_LIKE -> {
         TableIdentifier targetIdentifier =
@@ -229,8 +229,7 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
         resolutionManifest.addPath(
             new ResolverPath(
                 PolarisCatalogHelpers.tableIdentifierToList(targetIdentifier),
-                PolarisEntityType.TABLE_LIKE),
-            targetIdentifier);
+                PolarisEntityType.TABLE_LIKE));
       }
       default -> throw new IllegalArgumentException("Unsupported target type: " + target.getType());
     }
@@ -241,7 +240,8 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
 
     PolarisResolvedPathWrapper policyWrapper =
         resolutionManifest.getPassthroughResolvedPath(
-            identifier, PolarisEntityType.POLICY, PolarisEntitySubType.NULL_SUBTYPE);
+            ResolvedPathKey.ofPolicy(identifier.namespace(), identifier.name()),
+            PolarisEntitySubType.NULL_SUBTYPE);
     if (policyWrapper == null) {
       throw new NoSuchPolicyException(String.format("Policy does not exist: %s", identifier));
     }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogUtils.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogUtils.java
index ede8f864b0..705f2ead13 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogUtils.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogUtils.java
@@ -25,9 +25,9 @@ import jakarta.annotation.Nonnull;
 import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
-import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.service.types.PolicyAttachmentTarget;
 
 public class PolicyCatalogUtils {
@@ -40,7 +40,8 @@ public class PolicyCatalogUtils {
       case CATALOG -> resolutionManifest.getResolvedReferenceCatalogEntity();
       case NAMESPACE -> {
         var namespace = Namespace.of(target.getPath().toArray(new String[0]));
-        var resolvedTargetEntity = resolutionManifest.getResolvedPath(namespace);
+        var resolvedTargetEntity =
+            resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace));
         if (resolvedTargetEntity == null) {
           throw noSuchNamespaceException(namespace);
         }
@@ -51,7 +52,7 @@ public class PolicyCatalogUtils {
         // only Iceberg tables are supported
         var resolvedTableEntity =
             resolutionManifest.getResolvedPath(
-                tableIdentifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ICEBERG_TABLE);
+                ResolvedPathKey.ofTableLike(tableIdentifier), PolarisEntitySubType.ICEBERG_TABLE);
         if (resolvedTableEntity == null) {
           throw notFoundExceptionForTableLikeEntity(
               tableIdentifier, PolarisEntitySubType.ICEBERG_TABLE);
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
index 276315fb15..e2b7936de9 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
@@ -34,6 +34,7 @@ import org.apache.iceberg.exceptions.NotFoundException;
 import org.apache.polaris.core.PolarisCallContext;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.config.RealmConfig;
 import org.apache.polaris.core.context.CallContext;
@@ -53,6 +54,7 @@ import org.apache.polaris.core.persistence.dao.entity.GenerateEntityIdResult;
 import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
 import org.apache.polaris.core.secrets.UserSecretsManager;
 import org.apache.polaris.service.config.ReservedProperties;
@@ -153,9 +155,13 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(null);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(null);
 
     assertThatThrownBy(
             () ->
@@ -184,9 +190,13 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
     when(resolvedPathWrapper.getRawFullPath())
         .thenReturn(
             List.of(
@@ -239,9 +249,13 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(null);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(null);
 
     assertThatThrownBy(
             () ->
@@ -267,9 +281,13 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
     when(resolvedPathWrapper.getRawFullPath())
         .thenReturn(List.of(createEntity("wrong-catalog", PolarisEntityType.CATALOG)));
     when(resolvedPathWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
@@ -297,10 +315,14 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     PolarisEntity orgNsEntity = createNamespaceEntity(Namespace.of("org-ns"), 3L, 1L);
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
     when(resolvedPathWrapper.getRawFullPath()).thenReturn(List.of(catalogEntity, orgNsEntity));
     when(resolvedPathWrapper.getRawLeafEntity()).thenReturn(orgNsEntity);
 
@@ -328,7 +350,9 @@ public class PolarisAdminServiceTest {
 
     // Mock successful synthetic namespace resolution.
     PolarisResolvedPathWrapper syntheticPathWrapper = mock(PolarisResolvedPathWrapper.class);
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(syntheticPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(syntheticPathWrapper);
     when(syntheticPathWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
         .thenReturn(true);
 
@@ -365,13 +389,17 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     // Create a mock resolved path that returns null initially and is not fully resolved
     PolarisResolvedPathWrapper unresolvedWrapper = mock(PolarisResolvedPathWrapper.class);
     when(unresolvedWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
         .thenReturn(false);
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(unresolvedWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(unresolvedWrapper);
 
     // Should throw NotFoundException because feature is disabled and it's passthrough facade
     assertThatThrownBy(
@@ -400,10 +428,14 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     PolarisEntity orgNsEntity = createNamespaceEntity(Namespace.of("org-ns"), 3L, 1L);
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
     when(resolvedPathWrapper.getRawFullPath()).thenReturn(List.of(catalogEntity, orgNsEntity));
     when(resolvedPathWrapper.getRawLeafEntity()).thenReturn(orgNsEntity);
 
@@ -420,7 +452,8 @@ public class PolarisAdminServiceTest {
     // Mock getResolvedPath to return null for partial namespace
     PolarisResolvedPathWrapper partialPathWrapper = mock(PolarisResolvedPathWrapper.class);
     when(partialPathWrapper.getRawLeafEntity()).thenReturn(orgNsEntity);
-    when(resolutionManifest.getResolvedPath(eq(Namespace.of("org-ns", "team-ns"))))
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of("org-ns", "team-ns"), PolarisEntityType.NAMESPACE))))
         .thenReturn(partialPathWrapper);
 
     assertThatThrownBy(
@@ -447,7 +480,9 @@ public class PolarisAdminServiceTest {
     PolarisResolvedPathWrapper catalogRoleWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisEntity catalogRoleEntity = createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     PolarisEntity orgNsEntity = createNamespaceEntity(Namespace.of("org-ns"), 3L, 1L);
     PolarisEntity teamNsEntity = createNamespaceEntity(Namespace.of("org-ns", "team-ns"), 4L, 3L);
@@ -457,7 +492,11 @@ public class PolarisAdminServiceTest {
         .thenReturn(List.of(catalogEntity, orgNsEntity, teamNsEntity));
     when(existingPathWrapper.getRawLeafEntity()).thenReturn(teamNsEntity);
     when(resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE))
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE)),
+            eq(PolarisEntitySubType.ANY_SUBTYPE)))
         .thenReturn(existingPathWrapper);
     when(existingPathWrapper.getRawLeafEntity()).thenReturn(teamNsEntity);
 
@@ -475,7 +514,9 @@ public class PolarisAdminServiceTest {
     PolarisResolvedPathWrapper syntheticPathWrapper = mock(PolarisResolvedPathWrapper.class);
     when(syntheticPathWrapper.getRawFullPath())
         .thenReturn(List.of(catalogEntity, orgNsEntity, teamNsEntity, projectNsEntity));
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(syntheticPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(syntheticPathWrapper);
     when(syntheticPathWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
         .thenReturn(true);
     when(syntheticPathWrapper.getRawLeafEntity()).thenReturn(projectNsEntity);
@@ -492,7 +533,11 @@ public class PolarisAdminServiceTest {
     PolarisResolvedPathWrapper tablePathWrapper = mock(PolarisResolvedPathWrapper.class);
     when(tablePathWrapper.getRawLeafEntity()).thenReturn(tableEntity);
     when(resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE))
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE)),
+            eq(PolarisEntitySubType.ANY_SUBTYPE)))
         .thenReturn(tablePathWrapper);
 
     PrivilegeResult successResult = mock(PrivilegeResult.class);
@@ -530,7 +575,9 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     // Create a table entity for authorization but later it should not be found
     PolarisEntity tableEntity =
@@ -540,15 +587,21 @@ public class PolarisAdminServiceTest {
 
     // Mock authorization path with table
     when(resolutionManifest.getResolvedPath(
-            eq(identifier),
-            eq(PolarisEntityType.TABLE_LIKE),
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE)),
             eq(PolarisEntitySubType.ANY_SUBTYPE),
             eq(true)))
         .thenReturn(tableWrapper);
 
     // Mock the main resolution to return null (table not found in main logic)
     when(resolutionManifest.getResolvedPath(
-            eq(identifier), eq(PolarisEntityType.TABLE_LIKE), eq(PolarisEntitySubType.ANY_SUBTYPE)))
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE)),
+            eq(PolarisEntitySubType.ANY_SUBTYPE)))
         .thenReturn(null);
 
     // Should throw NoSuchTableException because feature is disabled
@@ -574,12 +627,18 @@ public class PolarisAdminServiceTest {
     PolarisResolvedPathWrapper catalogRoleWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisEntity catalogRoleEntity = createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     PolarisResolvedPathWrapper existingPathWrapper = mock(PolarisResolvedPathWrapper.class);
     when(existingPathWrapper.getRawFullPath()).thenReturn(List.of(catalogEntity));
     when(resolutionManifest.getResolvedPath(
-            identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE))
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE)),
+            eq(PolarisEntitySubType.ANY_SUBTYPE)))
         .thenReturn(existingPathWrapper);
     when(existingPathWrapper.getRawLeafEntity()).thenReturn(catalogEntity);
 
@@ -591,7 +650,12 @@ public class PolarisAdminServiceTest {
         .thenReturn(tableCreateResult);
     when(tableCreateResult.isSuccess()).thenReturn(false);
 
-    when(resolutionManifest.getResolvedPath(identifier)).thenReturn(existingPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(
+                ResolvedPathKey.of(
+                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
+                    PolarisEntityType.TABLE_LIKE))))
+        .thenReturn(existingPathWrapper);
     when(existingPathWrapper.getRawLeafEntity()).thenReturn(catalogEntity);
 
     assertThatThrownBy(
@@ -677,7 +741,9 @@ public class PolarisAdminServiceTest {
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
     when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
 
     PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
     when(resolutionManifest.getResolvedCatalogEntity()).thenReturn(CatalogEntity.of(catalogEntity));
@@ -686,7 +752,9 @@ public class PolarisAdminServiceTest {
     PolarisEntity catalogRoleEntity =
         createEntity(catalogRoleName, PolarisEntityType.CATALOG_ROLE, 2L);
     when(catalogRoleWrapper.getRawLeafEntity()).thenReturn(catalogRoleEntity);
-    when(resolutionManifest.getResolvedPath(eq(catalogRoleName))).thenReturn(catalogRoleWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(catalogRoleName), PolarisEntityType.CATALOG_ROLE))))
+        .thenReturn(catalogRoleWrapper);
 
     PolarisEntity namespaceEntity =
         createNamespaceEntity(Namespace.of(namespace.levels()[0]), 3L, 1L);
@@ -696,6 +764,8 @@ public class PolarisAdminServiceTest {
     when(resolvedPathWrapper.getRawLeafEntity()).thenReturn(namespaceEntity);
     when(resolvedPathWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
         .thenReturn(true);
-    when(resolutionManifest.getResolvedPath(eq(namespace))).thenReturn(resolvedPathWrapper);
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
+        .thenReturn(resolvedPathWrapper);
   }
 }
diff --git a/runtime/service/src/testFixtures/java/org/apache/polaris/service/catalog/PolarisPassthroughResolutionView.java b/runtime/service/src/testFixtures/java/org/apache/polaris/service/catalog/PolarisPassthroughResolutionView.java
index f25255d703..d71bcfaf7e 100644
--- a/runtime/service/src/testFixtures/java/org/apache/polaris/service/catalog/PolarisPassthroughResolutionView.java
+++ b/runtime/service/src/testFixtures/java/org/apache/polaris/service/catalog/PolarisPassthroughResolutionView.java
@@ -18,19 +18,16 @@
  */
 package org.apache.polaris.service.catalog;
 
-import java.util.Arrays;
-import org.apache.iceberg.catalog.Namespace;
-import org.apache.iceberg.catalog.TableIdentifier;
+import com.google.common.base.Preconditions;
 import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifestCatalogView;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverPath;
-import org.apache.polaris.service.types.PolicyIdentifier;
 
 /**
  * For test purposes or for elevated-privilege scenarios where entity resolution is allowed to
@@ -65,89 +62,52 @@ public class PolarisPassthroughResolutionView implements PolarisResolutionManife
   }
 
   @Override
-  public PolarisResolvedPathWrapper getResolvedPath(Object key) {
+  public PolarisResolvedPathWrapper getResolvedPath(ResolvedPathKey key) {
+    Preconditions.checkState(
+        key.entityType() == PolarisEntityType.NAMESPACE,
+        "Trying to getResolvedPath(key) for non-namespace key %s",
+        key);
     PolarisResolutionManifest manifest = newResolutionManifest();
-
-    if (key instanceof Namespace namespace) {
-      manifest.addPath(
-          new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE),
-          namespace);
-      manifest.resolveAll();
-      return manifest.getResolvedPath(namespace);
-    } else {
-      throw new IllegalStateException(
-          String.format(
-              "Trying to getResolvedPath(key) for %s with class %s", key, key.getClass()));
-    }
+    manifest.addPath(new ResolverPath(key.entityNames(), key.entityType()));
+    manifest.resolveAll();
+    return manifest.getResolvedPath(key);
   }
 
   @Override
   public PolarisResolvedPathWrapper getResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType) {
+      ResolvedPathKey key, PolarisEntitySubType subType) {
+    Preconditions.checkState(
+        key.entityType() == PolarisEntityType.TABLE_LIKE
+            || key.entityType() == PolarisEntityType.POLICY,
+        "Trying to getResolvedPath(key, subType) for unsupported key %s",
+        key);
     PolarisResolutionManifest manifest = newResolutionManifest();
-
-    if (key instanceof TableIdentifier identifier) {
-      manifest.addPath(
-          new ResolverPath(PolarisCatalogHelpers.tableIdentifierToList(identifier), entityType),
-          identifier);
-      manifest.resolveAll();
-      return manifest.getResolvedPath(identifier, entityType, subType);
-    } else if (key instanceof PolicyIdentifier policyIdentifier) {
-      manifest.addPath(
-          new ResolverPath(
-              PolarisCatalogHelpers.identifierToList(
-                  policyIdentifier.namespace(), policyIdentifier.name()),
-              entityType),
-          policyIdentifier);
-      manifest.resolveAll();
-      return manifest.getResolvedPath(policyIdentifier, entityType, subType);
-    } else {
-      throw new IllegalStateException(
-          String.format(
-              "Trying to getResolvedPath(key, subType) for %s with class %s and type %s / %s",
-              key, key.getClass(), entityType, subType));
-    }
+    manifest.addPath(new ResolverPath(key.entityNames(), key.entityType()));
+    manifest.resolveAll();
+    return manifest.getResolvedPath(key, subType);
   }
 
   @Override
-  public PolarisResolvedPathWrapper getPassthroughResolvedPath(Object key) {
+  public PolarisResolvedPathWrapper getPassthroughResolvedPath(ResolvedPathKey key) {
+    Preconditions.checkState(
+        key.entityType() == PolarisEntityType.NAMESPACE,
+        "Trying to getPassthroughResolvedPath(key) for non-namespace key %s",
+        key);
     PolarisResolutionManifest manifest = newResolutionManifest();
-
-    if (key instanceof Namespace namespace) {
-      manifest.addPassthroughPath(
-          new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE),
-          namespace);
-      return manifest.getPassthroughResolvedPath(namespace);
-    } else {
-      throw new IllegalStateException(
-          String.format(
-              "Trying to getResolvedPath(key) for %s with class %s", key, key.getClass()));
-    }
+    manifest.addPassthroughPath(new ResolverPath(key.entityNames(), key.entityType()));
+    return manifest.getPassthroughResolvedPath(key);
   }
 
   @Override
   public PolarisResolvedPathWrapper getPassthroughResolvedPath(
-      Object key, PolarisEntityType entityType, PolarisEntitySubType subType) {
+      ResolvedPathKey key, PolarisEntitySubType subType) {
+    Preconditions.checkState(
+        key.entityType() == PolarisEntityType.TABLE_LIKE
+            || key.entityType() == PolarisEntityType.POLICY,
+        "Trying to getPassthroughResolvedPath(key, subType) for unsupported key %s",
+        key);
     PolarisResolutionManifest manifest = newResolutionManifest();
-
-    if (key instanceof TableIdentifier identifier) {
-      manifest.addPassthroughPath(
-          new ResolverPath(PolarisCatalogHelpers.tableIdentifierToList(identifier), entityType),
-          identifier);
-      return manifest.getPassthroughResolvedPath(identifier, entityType, subType);
-    } else if (key instanceof PolicyIdentifier policyIdentifier) {
-      manifest.addPassthroughPath(
-          new ResolverPath(
-              PolarisCatalogHelpers.identifierToList(
-                  policyIdentifier.namespace(), policyIdentifier.name()),
-              entityType),
-          policyIdentifier);
-      return manifest.getPassthroughResolvedPath(policyIdentifier, entityType, subType);
-    } else {
-      throw new IllegalStateException(
-          String.format(
-              "Trying to getResolvedPath(key, subType) for %s with class %s and subType %s",
-              key, key.getClass(), subType));
-    }
+    manifest.addPassthroughPath(new ResolverPath(key.entityNames(), key.entityType()));
+    return manifest.getPassthroughResolvedPath(key, subType);
   }
 }