Repository: alibaba/nacos (2026-03-29T08:59:08Z)

Issue:
[BUG] nacos v3 api 和 grpc客户端机制不一致
  gRPC 和 v3 HTTP 的核心差异对比：

  gRPC 路径（正常）

  ServiceQueryRequestHandler.handle()
    cluster = request.getCluster() == null ? "" : request.getCluster()  // 空串
    → ServiceUtil.doSelectInstances(serviceInfo, "", ...)
    → clusterSets = StringUtils.isNotBlank("") ? ... : new HashSet<>()  // 空 Set
    → checkCluster(空Set, instance) → clusterSets.isEmpty() → return true  // 全部通过

  v3 HTTP 路径（有问题）

  InstanceListForm.fillDefaultValue()
    clusterName 为空 → 填充为 "DEFAULT"               // 强制覆盖
    → CatalogServiceV2Impl.listInstances(..., "DEFAULT")
    → serviceStorage.getClusters(service).contains("DEFAULT")  // false，cluster-hba ≠ DEFAULT
    → throw NacosException NOT_FOUND → controller 返回空 data


  结论： 两套路径用了完全不同的实现。gRPC 走 ServiceUtil.doSelectInstances，空 cluster 时 clusterSets 为空 Set，checkCluster 直接返回 true 放行所有实例。v3 HTTP 走
  CatalogServiceV2Impl.listInstances，InstanceListForm.fillDefaultValue() 把空 cluster 强制填成 "DEFAULT"，然后做精确匹配，找不到就抛异常。

  这是 v3 HTTP API 新增时没有复用 ServiceUtil 的过滤逻辑，导致行为不一致，属于 Bug。修复方式是 InstanceListForm.fillDefaultValue() 不应该对 clusterName
  做默认填充，或者 listInstances 里 clusterName 为空时走 listAllInstances 的逻辑。


Discussion:
InstanceListForm有多处使用，可能看一下是否根据场景区分。

client/admin/console 后两者基本等同。



Patch:
diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/CatalogServiceV2Impl.java b/naming/src/main/java/com/alibaba/nacos/naming/core/CatalogServiceV2Impl.java
index a87dfa34bd..53be53935f 100644
--- a/naming/src/main/java/com/alibaba/nacos/naming/core/CatalogServiceV2Impl.java
+++ b/naming/src/main/java/com/alibaba/nacos/naming/core/CatalogServiceV2Impl.java
@@ -113,7 +113,7 @@ public List<? extends Instance> listInstances(String namespaceId, String groupNa
             throw new NacosException(NacosException.NOT_FOUND,
                     String.format("service %s@@%s is not found!", groupName, serviceName));
         }
-        if (!serviceStorage.getClusters(service).contains(clusterName)) {
+        if (StringUtils.isNotBlank(clusterName) && !serviceStorage.getClusters(service).contains(clusterName)) {
             throw new NacosException(NacosException.NOT_FOUND, "cluster " + clusterName + " is not found!");
         }
         ServiceInfo serviceInfo = serviceStorage.getData(service);
diff --git a/naming/src/main/java/com/alibaba/nacos/naming/model/form/InstanceListForm.java b/naming/src/main/java/com/alibaba/nacos/naming/model/form/InstanceListForm.java
index 025677a0d2..788e2f4303 100644
--- a/naming/src/main/java/com/alibaba/nacos/naming/model/form/InstanceListForm.java
+++ b/naming/src/main/java/com/alibaba/nacos/naming/model/form/InstanceListForm.java
@@ -21,7 +21,6 @@
 import com.alibaba.nacos.api.model.v2.ErrorCode;
 import com.alibaba.nacos.common.utils.StringUtils;
 import com.alibaba.nacos.api.model.NacosForm;
-import com.alibaba.nacos.naming.misc.UtilsAndCommons;
 import org.springframework.http.HttpStatus;
 
 import java.util.Objects;
@@ -69,7 +68,7 @@ public void fillDefaultValue() {
             groupName = Constants.DEFAULT_GROUP;
         }
         if (StringUtils.isBlank(clusterName)) {
-            clusterName = UtilsAndCommons.DEFAULT_CLUSTER_NAME;
+            clusterName = StringUtils.EMPTY;
         }
         if (null == healthyOnly) {
             healthyOnly = false;


Test patch:
diff --git a/naming/src/test/java/com/alibaba/nacos/naming/controllers/v3/InstanceOpenApiControllerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/controllers/v3/InstanceOpenApiControllerTest.java
index 11cb590b87..d419bfc91e 100644
--- a/naming/src/test/java/com/alibaba/nacos/naming/controllers/v3/InstanceOpenApiControllerTest.java
+++ b/naming/src/test/java/com/alibaba/nacos/naming/controllers/v3/InstanceOpenApiControllerTest.java
@@ -18,6 +18,7 @@
 
 import com.alibaba.nacos.api.common.Constants;
 import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.common.utils.StringUtils;
 import com.alibaba.nacos.api.model.v2.ErrorCode;
 import com.alibaba.nacos.api.model.v2.Result;
 import com.alibaba.nacos.api.naming.NamingResponseCode;
@@ -119,7 +120,7 @@ void testList() throws Exception {
         serviceInfo.setName("test");
         serviceInfo.setHosts(Collections.singletonList(instance));
         when(instanceOperator.listInstance(Constants.DEFAULT_NAMESPACE_ID, Constants.DEFAULT_GROUP, "test", null,
-                Constants.DEFAULT_CLUSTER_NAME, false)).thenReturn(serviceInfo);
+                StringUtils.EMPTY, false)).thenReturn(serviceInfo);
         Result<List<Instance>> actual = instanceOpenApiController.list(instanceForm);
         assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode());
         assertEquals(ErrorCode.SUCCESS.getMsg(), actual.getMessage());
diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/CatalogServiceV2ImplTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/CatalogServiceV2ImplTest.java
index c081c11ef9..9359d6c9cb 100644
--- a/naming/src/test/java/com/alibaba/nacos/naming/core/CatalogServiceV2ImplTest.java
+++ b/naming/src/test/java/com/alibaba/nacos/naming/core/CatalogServiceV2ImplTest.java
@@ -131,7 +131,38 @@ void testListInstancesNonExistCluster() throws NacosException {
             catalogServiceV2Impl.listInstances("A", "B", "C", "DD");
         });
     }
-    
+
+    @Test
+    void testListInstancesWithBlankClusterNameReturnsAll() throws NacosException {
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.setGroupName("B");
+        serviceInfo.setName("C");
+        Instance instance1 = new Instance();
+        instance1.setClusterName("cluster1");
+        instance1.setIp("1.1.1.1");
+        Instance instance2 = new Instance();
+        instance2.setClusterName("cluster2");
+        instance2.setIp("2.2.2.2");
+        serviceInfo.setHosts(List.of(instance1, instance2));
+        Mockito.when(serviceStorage.getData(Mockito.any())).thenReturn(serviceInfo);
+        List<? extends Instance> instances = catalogServiceV2Impl.listInstances("A", "B", "C", "");
+        assertEquals(2, instances.size());
+    }
+
+    @Test
+    void testListInstancesWithNullClusterNameReturnsAll() throws NacosException {
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.setGroupName("B");
+        serviceInfo.setName("C");
+        Instance instance = new Instance();
+        instance.setClusterName("anyCluster");
+        instance.setIp("1.1.1.1");
+        serviceInfo.setHosts(Collections.singletonList(instance));
+        Mockito.when(serviceStorage.getData(Mockito.any())).thenReturn(serviceInfo);
+        List<? extends Instance> instances = catalogServiceV2Impl.listInstances("A", "B", "C", null);
+        assertEquals(1, instances.size());
+    }
+
     @Test
     void testPageListService() throws NacosException {
         ServiceInfo serviceInfo = new ServiceInfo();
diff --git a/naming/src/test/java/com/alibaba/nacos/naming/model/form/InstanceListFormTest.java b/naming/src/test/java/com/alibaba/nacos/naming/model/form/InstanceListFormTest.java
new file mode 100644
index 0000000000..840fa32eea
--- /dev/null
+++ b/naming/src/test/java/com/alibaba/nacos/naming/model/form/InstanceListFormTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2025 Alibaba Group Holding Ltd.
+ *
+ * Licensed 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 com.alibaba.nacos.naming.model.form;
+
+import com.alibaba.nacos.api.common.Constants;
+import com.alibaba.nacos.api.exception.api.NacosApiException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class InstanceListFormTest {
+
+    @Test
+    void testFillDefaultValueWhenClusterNameIsBlank() throws NacosApiException {
+        InstanceListForm form = new InstanceListForm();
+        form.setServiceName("testService");
+        form.validate();
+        assertEquals("", form.getClusterName());
+    }
+
+    @Test
+    void testFillDefaultValueWhenClusterNameIsProvided() throws NacosApiException {
+        InstanceListForm form = new InstanceListForm();
+        form.setServiceName("testService");
+        form.setClusterName("myCluster");
+        form.validate();
+        assertEquals("myCluster", form.getClusterName());
+    }
+
+    @Test
+    void testFillDefaultValueForNamespaceAndGroup() throws NacosApiException {
+        InstanceListForm form = new InstanceListForm();
+        form.setServiceName("testService");
+        form.validate();
+        assertEquals(Constants.DEFAULT_NAMESPACE_ID, form.getNamespaceId());
+        assertEquals(Constants.DEFAULT_GROUP, form.getGroupName());
+    }
+
+    @Test
+    void testValidateThrowsExceptionWhenServiceNameIsBlank() {
+        InstanceListForm form = new InstanceListForm();
+        assertThrows(NacosApiException.class, () -> form.validate());
+    }
+}