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

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new f2e5906a4b [Cherry-pick to branch-1.3] [#12879] fix(server): return 
accurate HTTP statuses for unsupported operations (#12880) (#12924)
f2e5906a4b is described below

commit f2e5906a4b105f7b146535a1dc5ca2418c375cf4
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 4 19:17:18 2026 +0800

    [Cherry-pick to branch-1.3] [#12879] fix(server): return accurate HTTP 
statuses for unsupported operations (#12880) (#12924)
    
    **Cherry-pick Information:**
    - Original commit: 8e41cedffb42064b240f93312e98610ee1788628
    - Target branch: `branch-1.3`
    - Status: ⚠️ **Has conflicts - manual resolution required**
    
    **Do not merge** until conflict markers are resolved and the
    `cherry-pick-conflict` label is removed.
    
    Please review and resolve the conflicts before merging.
    
    ---------
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../gravitino/client/TestRelationalCatalog.java    |  4 +-
 .../client/TestSupportsPartitionStatistics.java    | 11 +--
 .../gravitino/client/TestSupportsStatistics.java   | 11 +--
 docs/open-api/idp/idp.yaml                         |  8 +-
 docs/open-api/statistics.yaml                      | 62 +++++++++++---
 .../idp/storage/service/IdpGroupMetaService.java   |  6 +-
 .../org/apache/gravitino/idp/web/IdpRESTUtils.java | 10 ++-
 .../gravitino/idp/TestIdpUserGroupManager.java     |  3 +-
 .../idp/integration/test/IdpRESTApiIT.java         |  2 +-
 .../storage/service/TestIdpGroupMetaService.java   |  3 +-
 .../apache/gravitino/idp/web/TestIdpRESTUtils.java | 70 ++++++++++++++++
 .../org/apache/gravitino/server/web/Utils.java     | 48 ++++++++++-
 .../org/apache/gravitino/server/web/TestUtils.java | 29 ++++++-
 .../web/filter/AccessControlNotAllowedFilter.java  |  2 +-
 .../server/web/rest/ExceptionHandlers.java         | 19 +++++
 .../server/web/rest/StatisticOperations.java       | 12 +--
 .../filter/TestAccessControlNotAllowedFilter.java  |  9 +-
 .../server/web/rest/TestExceptionHandlers.java     | 54 ++++++++++++
 .../server/web/rest/TestStatisticOperations.java   | 95 +++++++++++++++++++++-
 web-v2/web/src/lib/store/metalakes/index.js        |  9 +-
 .../src/lib/utils/axios/unsupportedOperation.js    | 22 ++---
 .../lib/utils/axios/unsupportedOperation.test.js   | 43 ++++++++++
 22 files changed, 455 insertions(+), 77 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
index b10e9f981e..64feaa121c 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
@@ -25,8 +25,8 @@ import static 
org.apache.gravitino.rel.expressions.sorts.SortDirection.DESCENDIN
 import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
 import static org.apache.hc.core5.http.HttpStatus.SC_CONFLICT;
 import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
-import static org.apache.hc.core5.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
 import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.hc.core5.http.HttpStatus.SC_NOT_IMPLEMENTED;
 import static org.apache.hc.core5.http.HttpStatus.SC_OK;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -1186,7 +1186,7 @@ public class TestRelationalCatalog extends TestBase {
 
     // Test with exception
     ErrorResponse errorResp = ErrorResponse.unsupportedOperation("Unsupported 
operation");
-    buildMockResource(Method.DELETE, tablePath, null, errorResp, 
SC_METHOD_NOT_ALLOWED);
+    buildMockResource(Method.DELETE, tablePath, null, errorResp, 
SC_NOT_IMPLEMENTED);
 
     TableCatalog tableCatalog = catalog.asTableCatalog();
     Assertions.assertThrows(
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
index d19aaed1a6..fca28cae0d 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
@@ -19,8 +19,8 @@
 package org.apache.gravitino.client;
 
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -209,12 +209,7 @@ public class TestSupportsPartitionStatistics extends 
TestBase {
                 Collections.emptyList()));
     ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson, 
ErrorResponse.class);
     buildMockResource(
-        Method.PUT,
-        path,
-        Collections.emptyMap(),
-        expectedRequest,
-        unmodifiableError,
-        SC_METHOD_NOT_ALLOWED);
+        Method.PUT, path, Collections.emptyMap(), expectedRequest, 
unmodifiableError, SC_CONFLICT);
 
     Assertions.assertThrows(
         UnmodifiableStatisticException.class,
@@ -274,7 +269,7 @@ public class TestSupportsPartitionStatistics extends 
TestBase {
                 Collections.emptyList()));
     ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson, 
ErrorResponse.class);
     buildMockResource(
-        Method.POST, path, Collections.emptyMap(), null, unmodifiableError, 
SC_METHOD_NOT_ALLOWED);
+        Method.POST, path, Collections.emptyMap(), null, unmodifiableError, 
SC_CONFLICT);
 
     Assertions.assertThrows(
         UnmodifiableStatisticException.class,
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
index ab1c1c1bee..c588deeb2f 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
@@ -19,8 +19,8 @@
 package org.apache.gravitino.client;
 
 import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
+import static org.apache.hc.core5.http.HttpStatus.SC_CONFLICT;
 import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
-import static org.apache.hc.core5.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
 import static org.apache.hc.core5.http.HttpStatus.SC_OK;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -184,12 +184,7 @@ public class TestSupportsStatistics extends TestBase {
                 Collections.emptyList()));
     ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson, 
ErrorResponse.class);
     buildMockResource(
-        Method.PUT,
-        path,
-        Collections.emptyMap(),
-        expectedRequest,
-        unmodifiableError,
-        SC_METHOD_NOT_ALLOWED);
+        Method.PUT, path, Collections.emptyMap(), expectedRequest, 
unmodifiableError, SC_CONFLICT);
 
     Assertions.assertThrows(
         UnmodifiableStatisticException.class,
@@ -246,7 +241,7 @@ public class TestSupportsStatistics extends TestBase {
                 Collections.emptyList()));
     ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson, 
ErrorResponse.class);
     buildMockResource(
-        Method.POST, path, Collections.emptyMap(), null, unmodifiableError, 
SC_METHOD_NOT_ALLOWED);
+        Method.POST, path, Collections.emptyMap(), null, unmodifiableError, 
SC_CONFLICT);
 
     Assertions.assertThrows(
         UnmodifiableStatisticException.class,
diff --git a/docs/open-api/idp/idp.yaml b/docs/open-api/idp/idp.yaml
index 2437519f9d..a3c793f414 100644
--- a/docs/open-api/idp/idp.yaml
+++ b/docs/open-api/idp/idp.yaml
@@ -264,8 +264,8 @@ paths:
           $ref: "../openapi.yaml#/components/responses/RemoveResponse"
         "403":
           $ref: "#/components/responses/IdpForbiddenErrorResponse"
-        "405":
-          description: Method Not Allowed - The group is not empty and force 
is false
+        "409":
+          description: Conflict - The group is not empty and force is false
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -627,8 +627,8 @@ components:
 
     IdpGroupNotEmptyException:
       value: {
-        "code": 1006,
-        "type": "IllegalStateException",
+        "code": 1005,
+        "type": "NonEmptyEntityException",
         "message": "Failed to operate local user store group [engineers] 
operation [REMOVE], reason [IdP group engineers is not empty, use force=true to 
delete it]"
       }
 
diff --git a/docs/open-api/statistics.yaml b/docs/open-api/statistics.yaml
index 91e422be45..826a4b7ec1 100644
--- a/docs/open-api/statistics.yaml
+++ b/docs/open-api/statistics.yaml
@@ -41,6 +41,8 @@ paths:
                 examples:
                   StatisticListResponse:
                     $ref: "#/components/examples/StatisticListResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
         "404":
           description: Not Found - The specified metadata object doesn't exist
           content:
@@ -50,6 +52,12 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
+        "501":
+          description: Not Implemented - Statistics are not supported for the 
specified object
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
@@ -71,7 +79,7 @@ paths:
         "200":
           $ref: "./openapi.yaml#/components/responses/DropResponse"
         "400":
-          description: Bad Request - The request contains illegal statistic 
names
+          description: Bad Request - The object type or statistic names are 
invalid
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -88,8 +96,8 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
-        "405":
-          description: Method Not Allowed - The specified statistic is 
unmodifiable
+        "409":
+          description: Conflict - The specified statistic is unmodifiable
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -97,6 +105,12 @@ paths:
               examples:
                 UnmodifiableStatisticException:
                   $ref: "#/components/examples/UnmodifiableStatisticException"
+        "501":
+          description: Not Implemented - The requested statistics operation is 
not supported
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
@@ -118,7 +132,7 @@ paths:
         "200":
           $ref: "./openapi.yaml#/components/responses/BaseResponse"
         "400":
-          description: Bad Request - The request contains illegal statistic 
names
+          description: Bad Request - The object type or statistic names are 
invalid
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -135,8 +149,8 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
-        "405":
-          description: Method Not Allowed - The specified statistic is 
unmodifiable
+        "409":
+          description: Conflict - The specified statistic is unmodifiable
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -144,6 +158,12 @@ paths:
               examples:
                 UnmodifiableStatisticException:
                   $ref: "#/components/examples/UnmodifiableStatisticException"
+        "501":
+          description: Not Implemented - The requested statistics operation is 
not supported
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
@@ -184,6 +204,12 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
+        "501":
+          description: Not Implemented - Partition statistics are not 
supported for the specified object
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
@@ -205,7 +231,7 @@ paths:
         "200":
           $ref: "./openapi.yaml#/components/responses/DropResponse"
         "400":
-          description: Bad Request - The request contains illegal statistic 
names
+          description: Bad Request - The object type or statistic names are 
invalid
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -222,8 +248,8 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
-        "405":
-          description: Method Not Allowed - The specified statistic is 
unmodifiable
+        "409":
+          description: Conflict - The specified statistic is unmodifiable
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -231,6 +257,12 @@ paths:
               examples:
                 UnmodifiableStatisticException:
                   $ref: "#/components/examples/UnmodifiableStatisticException"
+        "501":
+          description: Not Implemented - The requested partition statistics 
operation is not supported
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
@@ -252,7 +284,7 @@ paths:
         "200":
           $ref: "./openapi.yaml#/components/responses/BaseResponse"
         "400":
-          description: Bad Request - The request contains illegal statistic 
names
+          description: Bad Request - The object type or statistic names are 
invalid
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -269,8 +301,8 @@ paths:
               examples:
                 NoSuchMetadataObjectException:
                   $ref: "#/components/examples/NoSuchMetadataObjectException"
-        "405":
-          description: Method Not Allowed - The specified statistic is 
unmodifiable
+        "409":
+          description: Conflict - The specified statistic is unmodifiable
           content:
             application/vnd.gravitino.v1+json:
               schema:
@@ -278,6 +310,12 @@ paths:
               examples:
                 UnmodifiableStatisticException:
                   $ref: "#/components/examples/UnmodifiableStatisticException"
+        "501":
+          description: Not Implemented - The requested partition statistics 
operation is not supported
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
index c4bfdd3601..018b20fe1a 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
 import org.apache.gravitino.exceptions.NotFoundException;
 import org.apache.gravitino.idp.model.IdpGroup;
 import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
@@ -104,14 +105,15 @@ public class IdpGroupMetaService {
    * @param force when false, rejects deletion if the group still has members; 
when true, removes
    *     memberships and deletes the group
    * @return true if the group was deleted
+   * @throws NonEmptyEntityException if the group has members and force is 
false
    */
   @Monitored(
       metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
       baseMetricName = "deleteIdpGroup")
   public boolean deleteIdpGroup(String groupName, boolean force) {
     if (!force && !listUsernamesByGroupName(groupName).isEmpty()) {
-      throw new IllegalStateException(
-          String.format("IdP group %s is not empty, use force=true to delete 
it", groupName));
+      throw new NonEmptyEntityException(
+          "IdP group %s is not empty, use force=true to delete it", groupName);
     }
 
     int[] deletedCount = new int[] {0};
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
index 75ab056738..95aaa1b9fd 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
@@ -27,6 +27,7 @@ import org.apache.gravitino.UserPrincipal;
 import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
 import org.apache.gravitino.exceptions.NotFoundException;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.slf4j.Logger;
@@ -103,7 +104,7 @@ public final class IdpRESTUtils {
 
   public static Response unsupportedOperation(String message, Throwable 
throwable) {
     return json(
-        Response.Status.METHOD_NOT_ALLOWED, 
ErrorResponse.unsupportedOperation(message, throwable));
+        Response.Status.NOT_IMPLEMENTED, 
ErrorResponse.unsupportedOperation(message, throwable));
   }
 
   public static Response internalError(String message, Throwable throwable) {
@@ -121,7 +122,12 @@ public final class IdpRESTUtils {
     if (e instanceof AlreadyExistsException) {
       return alreadyExists(errorMsg, e);
     }
-    if (e instanceof IllegalStateException || e instanceof 
UnsupportedOperationException) {
+    if (e instanceof NonEmptyEntityException) {
+      return json(
+          Response.Status.CONFLICT,
+          ErrorResponse.nonEmpty(e.getClass().getSimpleName(), errorMsg, e));
+    }
+    if (e instanceof UnsupportedOperationException) {
       return unsupportedOperation(errorMsg, e);
     }
     return internalError(errorMsg, e);
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
index 419b688dcf..2ce53469d0 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
@@ -40,6 +40,7 @@ import java.util.stream.Stream;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.auth.AuthenticatorType;
 import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
 import org.apache.gravitino.exceptions.NotFoundException;
 import org.apache.gravitino.idp.basic.IdpCredentialValidator;
 import org.apache.gravitino.idp.model.IdpGroup;
@@ -193,7 +194,7 @@ public class TestIdpUserGroupManager {
     manager.changeGroupMembership("testRemoveGroup", 
Lists.newArrayList("groupMember"), null);
 
     Assertions.assertThrows(
-        IllegalStateException.class, () -> 
manager.removeGroup("testRemoveGroup", false));
+        NonEmptyEntityException.class, () -> 
manager.removeGroup("testRemoveGroup", false));
 
     manager.changeGroupMembership("testRemoveGroup", null, 
Lists.newArrayList("groupMember"));
     Assertions.assertTrue(manager.removeGroup("testRemoveGroup", false));
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
index ca18bfcf6c..aaa3d91d5a 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
@@ -208,7 +208,7 @@ public class IdpRESTApiIT extends BaseIT {
         putMembership(GROUP1, new GroupMembershipChangeRequest(new String[] 
{USER1, USER2}, null));
     Assertions.assertEquals(Set.of(USER1, USER2), 
Set.copyOf(group.getGroup().users()));
 
-    assertError(405, deleteGroupResponse(GROUP1, false), 
ErrorConstants.UNSUPPORTED_OPERATION_CODE);
+    assertError(409, deleteGroupResponse(GROUP1, false), 
ErrorConstants.NON_EMPTY_CODE);
 
     group =
         putMembership(GROUP1, new GroupMembershipChangeRequest(null, new 
String[] {USER1, USER2}));
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
index b0b22e7f21..70067d66ac 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
@@ -28,6 +28,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
 import org.apache.gravitino.exceptions.NotFoundException;
 import org.apache.gravitino.idp.storage.po.IdpGroupPO;
 import org.junit.jupiter.api.Tag;
@@ -121,7 +122,7 @@ class TestIdpGroupMetaService extends 
AbstractIdpMetaServiceTest {
     IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
 
     assertThrows(
-        IllegalStateException.class, () -> 
groupMetaService.deleteIdpGroup("group1", false));
+        NonEmptyEntityException.class, () -> 
groupMetaService.deleteIdpGroup("group1", false));
 
     runServiceCall(() -> assertTrue(groupMetaService.deleteIdpGroup("group1", 
true)));
     assertNull(idpGroupMetaMapper.selectIdpGroup("group1"));
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
new file mode 100644
index 0000000000..3b8840fd14
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gravitino.idp.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
+import org.junit.jupiter.api.Test;
+
+class TestIdpRESTUtils {
+
+  @Test
+  void testUnsupportedOperationReturnsNotImplemented() {
+    Response response =
+        IdpRESTUtils.handleException(
+            "group",
+            IdpOperationType.GET,
+            "group",
+            new UnsupportedOperationException("unsupported"));
+
+    assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), 
response.getStatus());
+    ErrorResponse error = (ErrorResponse) response.getEntity();
+    assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, error.getCode());
+  }
+
+  @Test
+  void testNonEmptyEntityReturnsConflict() {
+    Response response =
+        IdpRESTUtils.handleException(
+            "group",
+            IdpOperationType.REMOVE,
+            "group",
+            new NonEmptyEntityException("Group is not empty"));
+
+    assertEquals(Response.Status.CONFLICT.getStatusCode(), 
response.getStatus());
+    ErrorResponse error = (ErrorResponse) response.getEntity();
+    assertEquals(ErrorConstants.NON_EMPTY_CODE, error.getCode());
+    assertEquals(NonEmptyEntityException.class.getSimpleName(), 
error.getType());
+  }
+
+  @Test
+  void testUnexpectedIllegalStateReturnsInternalError() {
+    Response response =
+        IdpRESTUtils.handleException(
+            "group", IdpOperationType.GET, "group", new 
IllegalStateException("invalid state"));
+
+    assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
response.getStatus());
+    ErrorResponse error = (ErrorResponse) response.getEntity();
+    assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, error.getCode());
+  }
+}
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java 
b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
index 4ef45b6a50..4942be851e 100644
--- a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
+++ b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
@@ -173,17 +173,63 @@ public class Utils {
         .build();
   }
 
+  /**
+   * Returns an HTTP 501 response for functionality that the server does not 
implement.
+   *
+   * @param message the error message
+   * @return the HTTP response
+   */
   public static Response unsupportedOperation(String message) {
     return unsupportedOperation(message, null);
   }
 
+  /**
+   * Returns an HTTP 501 response for functionality that the server does not 
implement.
+   *
+   * @param message the error message
+   * @param throwable the exception that caused the error
+   * @return the HTTP response
+   */
   public static Response unsupportedOperation(String message, Throwable 
throwable) {
-    return Response.status(Response.Status.METHOD_NOT_ALLOWED)
+    return Response.status(Response.Status.NOT_IMPLEMENTED)
+        .entity(ErrorResponse.unsupportedOperation(message, throwable))
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
+  /**
+   * Returns an HTTP 409 response when an operation conflicts with the target 
object's state.
+   *
+   * <p>The unsupported-operation error payload is retained so existing 
clients can reconstruct
+   * domain exceptions such as {@code UnmodifiableStatisticException}.
+   *
+   * @param message the error message
+   * @param throwable the exception that caused the error
+   * @return the HTTP response
+   */
+  public static Response operationConflict(String message, Throwable 
throwable) {
+    return Response.status(Response.Status.CONFLICT)
         .entity(ErrorResponse.unsupportedOperation(message, throwable))
         .type(MediaType.APPLICATION_JSON)
         .build();
   }
 
+  /**
+   * Returns an HTTP 405 response when the target resource does not allow the 
request method.
+   *
+   * <p>The unsupported-operation error payload is retained for compatibility 
with clients that
+   * identify this response by its application error code.
+   *
+   * @param message the error message
+   * @return the HTTP response
+   */
+  public static Response methodNotAllowed(String message) {
+    return Response.status(Response.Status.METHOD_NOT_ALLOWED)
+        .entity(ErrorResponse.unsupportedOperation(message))
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
   public static Response forbidden(String message, Throwable throwable) {
     return Response.status(Response.Status.FORBIDDEN)
         .entity(ErrorResponse.forbidden(message, throwable))
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
index c495a6275b..0e8d5023b3 100644
--- a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
+++ b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
@@ -30,7 +30,9 @@ import javax.ws.rs.core.Response;
 import org.apache.gravitino.audit.FilesetAuditConstants;
 import org.apache.gravitino.audit.FilesetDataOperation;
 import org.apache.gravitino.audit.InternalClientType;
+import org.apache.gravitino.dto.responses.ErrorConstants;
 import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
@@ -168,12 +170,37 @@ public class TestUtils {
   public void testUnsupportedOperation() {
     Response response = Utils.unsupportedOperation("Unsupported operation");
     assertNotNull(response);
-    assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
response.getStatus());
+    assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), 
response.getStatus());
     assertEquals(MediaType.APPLICATION_JSON, 
response.getMediaType().toString());
     ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
     assertEquals("Unsupported operation", errorResponse.getMessage());
   }
 
+  @Test
+  public void testOperationConflict() {
+    UnmodifiableStatisticException exception =
+        new UnmodifiableStatisticException("Unmodifiable statistic");
+    Response response = Utils.operationConflict(exception.getMessage(), 
exception);
+
+    assertNotNull(response);
+    assertEquals(Response.Status.CONFLICT.getStatusCode(), 
response.getStatus());
+    assertEquals(MediaType.APPLICATION_JSON, 
response.getMediaType().toString());
+    ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+    assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
errorResponse.getCode());
+    assertEquals(UnmodifiableStatisticException.class.getSimpleName(), 
errorResponse.getType());
+  }
+
+  @Test
+  public void testMethodNotAllowed() {
+    Response response = Utils.methodNotAllowed("Method not allowed");
+
+    assertNotNull(response);
+    assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
response.getStatus());
+    assertEquals(MediaType.APPLICATION_JSON, 
response.getMediaType().toString());
+    ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+    assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
errorResponse.getCode());
+  }
+
   @Test
   public void testFilterFilesetAuditHeaders() {
     // test invalid internal client type
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
index 42c001f766..9a4b6761e2 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
@@ -40,7 +40,7 @@ public class AccessControlNotAllowedFilter implements 
ContainerRequestFilter {
   @Override
   public void filter(ContainerRequestContext requestContext) throws 
IOException {
     requestContext.abortWith(
-        Utils.unsupportedOperation(
+        Utils.methodNotAllowed(
             String.format(
                 "You should set '%s' to true in the server side 
`gravitino.conf`"
                     + " to enable the authorization of the system, otherwise 
these interfaces can't work.",
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index a4efa56771..a6e3e38a43 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -52,6 +52,7 @@ import 
org.apache.gravitino.exceptions.TableAlreadyExistsException;
 import org.apache.gravitino.exceptions.TagAlreadyAssociatedException;
 import org.apache.gravitino.exceptions.TagAlreadyExistsException;
 import org.apache.gravitino.exceptions.TopicAlreadyExistsException;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
 import org.apache.gravitino.exceptions.UserAlreadyExistsException;
 import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
 import org.apache.gravitino.server.web.Utils;
@@ -1054,6 +1055,9 @@ public class ExceptionHandlers {
       } else if (e instanceof NotFoundException) {
         return Utils.notFound(errorMsg, e);
 
+      } else if (e instanceof UnmodifiableStatisticException) {
+        return Utils.operationConflict(errorMsg, e);
+
       } else if (e instanceof UnsupportedOperationException) {
         return Utils.unsupportedOperation(errorMsg, e);
 
@@ -1086,6 +1090,9 @@ public class ExceptionHandlers {
       } else if (e instanceof NotFoundException) {
         return Utils.notFound(errorMsg, e);
 
+      } else if (e instanceof UnmodifiableStatisticException) {
+        return Utils.operationConflict(errorMsg, e);
+
       } else if (e instanceof UnsupportedOperationException) {
         return Utils.unsupportedOperation(errorMsg, e);
 
@@ -1114,6 +1121,18 @@ public class ExceptionHandlers {
 
       String errorMsg =
           getBaseErrorMsg(formattedObject, op.name(), formattedParent, 
getErrorMsg(e));
+      // Classify domain-specific UnsupportedOperationException subclasses 
before the generic
+      // capability fallback below.
+      if (e instanceof UnmodifiableStatisticException) {
+        LOG.warn(errorMsg, e);
+        return Utils.operationConflict(errorMsg, e);
+      }
+
+      if (e instanceof UnsupportedOperationException) {
+        LOG.warn(errorMsg, e);
+        return Utils.unsupportedOperation(errorMsg, e);
+      }
+
       LOG.error(errorMsg, e);
       return Utils.internalError(errorMsg, e);
     }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
index f94af2f44d..d698185fdd 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
@@ -119,7 +119,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Listing statistics is only supported for tables now.");
             }
 
@@ -170,7 +170,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Update statistics is only supported for tables now.");
             }
 
@@ -227,7 +227,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Dropping statistics is only supported for tables now.");
             }
 
@@ -280,7 +280,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Listing partition statistics is only supported for tables 
now.");
             }
 
@@ -364,7 +364,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Updating partition statistics is only supported for tables 
now.");
             }
 
@@ -430,7 +430,7 @@ public class StatisticOperations {
                 MetadataObjects.parse(
                     fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
             if (object.type() != MetadataObject.Type.TABLE) {
-              throw new UnsupportedOperationException(
+              throw new IllegalArgumentException(
                   "Dropping partition statistics is only supported for tables 
now.");
             }
 
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
index 54b3f3c669..178884a3f2 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
@@ -18,13 +18,15 @@
  */
 package org.apache.gravitino.server.web.filter;
 
-import static org.mockito.ArgumentMatchers.any;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
 import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 
 public class TestAccessControlNotAllowedFilter {
   @Test
@@ -32,6 +34,9 @@ public class TestAccessControlNotAllowedFilter {
     AccessControlNotAllowedFilter filter = new AccessControlNotAllowedFilter();
     ContainerRequestContext requestContext = 
mock(ContainerRequestContext.class);
     filter.filter(requestContext);
-    verify(requestContext).abortWith(any());
+    ArgumentCaptor<Response> responseCaptor = 
ArgumentCaptor.forClass(Response.class);
+    verify(requestContext).abortWith(responseCaptor.capture());
+    assertEquals(
+        Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
responseCaptor.getValue().getStatus());
   }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
index bdc43c4fc2..60140578cf 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
@@ -18,6 +18,11 @@
  */
 package org.apache.gravitino.server.web.rest;
 
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -50,4 +55,53 @@ public class TestExceptionHandlers {
     String msg6 = ExceptionHandlers.BaseExceptionHandler.getErrorMsg(e6);
     Assertions.assertEquals("", msg6);
   }
+
+  @Test
+  void testUnsupportedOperationReturnsNotImplemented() {
+    UnsupportedOperationException exception =
+        new UnsupportedOperationException("Operation is not supported");
+    List<Response> responses =
+        List.of(
+            ExceptionHandlers.handleTableException(
+                OperationType.ALTER, "table", "schema", exception),
+            ExceptionHandlers.handlePolicyException(
+                OperationType.LIST, "policy", "metalake", exception),
+            new ExceptionHandlers.BaseExceptionHandler()
+                .handle(OperationType.LIST, "object", "parent", exception));
+
+    responses.forEach(
+        response -> {
+          Assertions.assertEquals(
+              Response.Status.NOT_IMPLEMENTED.getStatusCode(), 
response.getStatus());
+          ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+          Assertions.assertEquals(
+              ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
errorResponse.getCode());
+          Assertions.assertEquals(
+              UnsupportedOperationException.class.getSimpleName(), 
errorResponse.getType());
+        });
+  }
+
+  @Test
+  void testUnmodifiableOperationReturnsConflict() {
+    UnmodifiableStatisticException exception =
+        new UnmodifiableStatisticException("Statistic is unmodifiable");
+    List<Response> responses =
+        List.of(
+            ExceptionHandlers.handleStatisticException(
+                OperationType.ALTER, "statistic", "table", exception),
+            ExceptionHandlers.handlePartitionStatsException(
+                OperationType.ALTER, "partition", "table", exception),
+            new ExceptionHandlers.BaseExceptionHandler()
+                .handle(OperationType.ALTER, "statistic", "table", exception));
+
+    responses.forEach(
+        response -> {
+          Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
response.getStatus());
+          ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+          Assertions.assertEquals(
+              ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
errorResponse.getCode());
+          Assertions.assertEquals(
+              UnmodifiableStatisticException.class.getSimpleName(), 
errorResponse.getType());
+        });
+  }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
index f8951a9966..751ee690c2 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
@@ -236,6 +236,93 @@ public class TestStatisticOperations extends 
BaseOperationsTest {
     ErrorResponse errorResp2 = resp2.readEntity(ErrorResponse.class);
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp2.getCode());
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp2.getType());
+
+    // Test throw UnsupportedOperationException
+    doThrow(new UnsupportedOperationException("mock error"))
+        .when(manager)
+        .listStatistics(any(), any());
+    Response resp3 =
+        target(
+                "/metalakes/"
+                    + metalake
+                    + "/objects/"
+                    + tableObject.type()
+                    + "/"
+                    + tableObject.fullName()
+                    + "/statistics")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), 
resp3.getStatus());
+    ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
errorResp3.getCode());
+    Assertions.assertEquals(
+        UnsupportedOperationException.class.getSimpleName(), 
errorResp3.getType());
+  }
+
+  /** Tests that every statistics endpoint rejects non-table object types as 
invalid arguments. */
+  @Test
+  public void testStatisticsEndpointsRejectNonTableObject() {
+    MetadataObject catalogObject = MetadataObjects.parse(catalog, 
MetadataObject.Type.CATALOG);
+    String path =
+        "/metalakes/"
+            + metalake
+            + "/objects/"
+            + catalogObject.type()
+            + "/"
+            + catalogObject.fullName()
+            + "/statistics";
+    Map<String, StatisticValue<?>> statistics =
+        Map.of(Statistic.CUSTOM_PREFIX + "test", 
StatisticValues.longValue(1L));
+    StatisticsUpdateRequest updateRequest = new 
StatisticsUpdateRequest(statistics);
+    StatisticsDropRequest dropRequest =
+        new StatisticsDropRequest(new String[] {Statistic.CUSTOM_PREFIX + 
"test"});
+    PartitionStatisticsUpdateRequest partitionUpdateRequest =
+        new PartitionStatisticsUpdateRequest(
+            List.of(PartitionStatisticsUpdateDTO.of("partition", statistics)));
+    PartitionStatisticsDropRequest partitionDropRequest =
+        new PartitionStatisticsDropRequest(
+            List.of(
+                PartitionStatisticsDropDTO.of(
+                    "partition", List.of(Statistic.CUSTOM_PREFIX + "test"))));
+
+    List<Response> responses =
+        List.of(
+            target(path)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .get(),
+            target(path)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .put(entity(updateRequest, MediaType.APPLICATION_JSON_TYPE)),
+            target(path)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .post(entity(dropRequest, MediaType.APPLICATION_JSON_TYPE)),
+            target(path + "/partitions")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .get(),
+            target(path + "/partitions")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .put(entity(partitionUpdateRequest, 
MediaType.APPLICATION_JSON_TYPE)),
+            target(path + "/partitions")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .accept("application/vnd.gravitino.v1+json")
+                .post(entity(partitionDropRequest, 
MediaType.APPLICATION_JSON_TYPE)));
+
+    responses.forEach(
+        response -> {
+          Assertions.assertEquals(
+              Response.Status.BAD_REQUEST.getStatusCode(), 
response.getStatus());
+          ErrorResponse errorResponse = 
response.readEntity(ErrorResponse.class);
+          Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
errorResponse.getCode());
+          Assertions.assertEquals(
+              IllegalArgumentException.class.getSimpleName(), 
errorResponse.getType());
+        });
   }
 
   @Test
@@ -363,7 +450,7 @@ public class TestStatisticOperations extends 
BaseOperationsTest {
             .accept("application/vnd.gravitino.v1+json")
             .put(entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
resp4.getStatus());
+    Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
resp4.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp4.getMediaType());
 
     ErrorResponse errorResp4 = resp4.readEntity(ErrorResponse.class);
@@ -619,7 +706,7 @@ public class TestStatisticOperations extends 
BaseOperationsTest {
             .accept("application/vnd.gravitino.v1+json")
             .post(entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
resp3.getStatus());
+    Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
resp3.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp3.getMediaType());
 
     ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
@@ -878,7 +965,7 @@ public class TestStatisticOperations extends 
BaseOperationsTest {
             .accept("application/vnd.gravitino.v1+json")
             .put(entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
resp4.getStatus());
+    Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
resp4.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp4.getMediaType());
 
     ErrorResponse errorResp4 = resp4.readEntity(ErrorResponse.class);
@@ -1050,7 +1137,7 @@ public class TestStatisticOperations extends 
BaseOperationsTest {
             .accept("application/vnd.gravitino.v1+json")
             .post(entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
resp3.getStatus());
+    Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
resp3.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp3.getMediaType());
 
     ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
diff --git a/web-v2/web/src/lib/store/metalakes/index.js 
b/web-v2/web/src/lib/store/metalakes/index.js
index 55566d6973..49283333f9 100644
--- a/web-v2/web/src/lib/store/metalakes/index.js
+++ b/web-v2/web/src/lib/store/metalakes/index.js
@@ -20,6 +20,7 @@
 import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
 
 import { to, extractPlaceholder, updateTreeData, findInTree } from 
'@/lib/utils'
+import { isUnsupportedOperationError } from 
'@/lib/utils/axios/unsupportedOperation'
 import toast from 'react-hot-toast'
 
 import _ from 'lodash-es'
@@ -2250,8 +2251,8 @@ export const fetchViews = createAsyncThunk(
     const [err, res] = await to(getViewsApi({ metalake, catalog, schema }, { 
errorMessageMode: 'none' }))
 
     if (err || !res) {
-      // Catalog doesn't support views (HTTP 405) — return empty views silently
-      if (err?.response?.status === 405) {
+      // Catalog doesn't support views (HTTP 501, or legacy 405 with code 
1006) — return empty views silently
+      if (isUnsupportedOperationError(err)) {
         return { views: [], init }
       }
       if (init) {
@@ -2304,8 +2305,8 @@ export const getViewDetails = createAsyncThunk(
     const [err, res] = await to(getViewDetailsApi({ metalake, catalog, schema, 
view }, { errorMessageMode: 'none' }))
 
     if (err || !res) {
-      // Catalog doesn't support views (HTTP 405) — return empty result 
silently
-      if (err?.response?.status === 405) {
+      // Catalog doesn't support views (HTTP 501, or legacy 405 with code 
1006) — return empty result silently
+      if (isUnsupportedOperationError(err)) {
         return { view: null, init }
       }
       throw new Error(err)
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
 b/web-v2/web/src/lib/utils/axios/unsupportedOperation.js
similarity index 54%
copy from 
server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
copy to web-v2/web/src/lib/utils/axios/unsupportedOperation.js
index 54b3f3c669..bc9fb5c616 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
+++ b/web-v2/web/src/lib/utils/axios/unsupportedOperation.js
@@ -16,22 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.gravitino.server.web.filter;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
+const UNSUPPORTED_OPERATION_CODE = 1006
 
-import java.io.IOException;
-import javax.ws.rs.container.ContainerRequestContext;
-import org.junit.jupiter.api.Test;
-
-public class TestAccessControlNotAllowedFilter {
-  @Test
-  public void testAccessControlNotAllowedFilter() throws IOException {
-    AccessControlNotAllowedFilter filter = new AccessControlNotAllowedFilter();
-    ContainerRequestContext requestContext = 
mock(ContainerRequestContext.class);
-    filter.filter(requestContext);
-    verify(requestContext).abortWith(any());
-  }
-}
+/** Returns whether an HTTP client error represents an unsupported server 
operation. */
+export const isUnsupportedOperationError = error =>
+  error?.response?.status === 501 ||
+  (error?.response?.status === 405 && error?.response?.data?.code === 
UNSUPPORTED_OPERATION_CODE)
diff --git a/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js 
b/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js
new file mode 100644
index 0000000000..d8ef382d9b
--- /dev/null
+++ b/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+import { describe, expect, it } from 'vitest'
+import { isUnsupportedOperationError } from 
'@/lib/utils/axios/unsupportedOperation'
+
+describe('isUnsupportedOperationError', () => {
+  it('recognizes HTTP 501 as an unsupported operation', () => {
+    expect(isUnsupportedOperationError({ response: { status: 501 } 
})).toBe(true)
+  })
+
+  it('recognizes the legacy HTTP 405 response by its application error code', 
() => {
+    expect(isUnsupportedOperationError({ response: { status: 405, data: { 
code: 1006 } } })).toBe(true)
+  })
+
+  it('does not hide an actual HTTP method mismatch', () => {
+    expect(isUnsupportedOperationError({ response: { status: 405, data: { 
code: 1000 } } })).toBe(false)
+  })
+
+  it('does not treat an HTTP conflict as an unsupported operation', () => {
+    expect(isUnsupportedOperationError({ response: { status: 409, data: { 
code: 1006 } } })).toBe(false)
+  })
+
+  it('handles errors without an HTTP response', () => {
+    expect(isUnsupportedOperationError(new Error('network error'))).toBe(false)
+  })
+})

Reply via email to