alibaba/nacos · error · NacosApiException

21009

21009

Error message

Service {service.getGroupedServiceName()} is not empty, can't be delete. Please unregister instance first

What it means

Thrown by ServiceOperatorV2Impl.delete(Service) when the service singleton exists but serviceStorage.getPushData(service).getHosts() is non-empty — meaning the service still has registered instances. Error code 21009 (SERVICE_DELETE_FAILURE), NacosException.INVALID_PARAM (400). Nacos refuses to delete a service that still hosts instances to prevent orphaned references.

Source

Thrown at naming/src/main/java/com/alibaba/nacos/naming/core/ServiceOperatorV2Impl.java:134

    public void delete(String namespaceId, String serviceName) throws NacosException {
        Service service = getServiceFromGroupedServiceName(namespaceId, serviceName, true);
        delete(service);
    }
    
    /**
     * Delete service.
     *
     * @param service service v2
     * @throws NacosException nacos exception during delete
     */
    public void delete(Service service) throws NacosException {
        if (!ServiceManager.getInstance().containSingleton(service)) {
            throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SERVICE_NOT_EXIST,
                String.format("service %s not found!", service.getGroupedServiceName()));
        }
        
        if (!serviceStorage.getPushData(service).getHosts().isEmpty()) {
            throw new NacosApiException(NacosException.INVALID_PARAM,
                ErrorCode.SERVICE_DELETE_FAILURE,
                "Service " + service.getGroupedServiceName()
                    + " is not empty, can't be delete. Please unregister instance first");
        }
        metadataOperateService.deleteServiceMetadata(service);
    }
    
    @Override
    public ObjectNode queryService(String namespaceId, String serviceName) throws NacosException {
        Service service = getServiceFromGroupedServiceName(namespaceId, serviceName, true);
        if (!ServiceManager.getInstance().containSingleton(service)) {
            throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SERVICE_NOT_EXIST,
                "service not found, namespace: " + namespaceId + ", serviceName: " + serviceName);
        }
        ObjectNode result = JacksonUtils.createEmptyJsonNode();
        ServiceMetadata serviceMetadata =
            metadataManager.getServiceMetadata(service).orElse(new ServiceMetadata());
        setServiceMetadata(result, serviceMetadata, service);

View on GitHub (pinned to 9b989acdf1)

Solutions

  1. Deregister all instances for the service first (DELETE instance API or gRPC deregister), then retry delete.
  2. Stop all client processes that register instances against this service, then wait for ephemeral instances to expire.
  3. For persistent instances, ensure the CP/Raft log entry is removed via the deregister path (not just in-memory).
  4. Verify the host list is empty via selectInstances before calling delete.

Example fix

// before
serviceOperatorV2.delete(namespaceId, serviceName);

// after — drain instances first
ServiceInfo info = serviceStorage.getData(service);
for (Instance h : info.getHosts()) {
    instanceOperator.deregisterInstance(namespaceId, group, name,
        h.getIp(), h.getPort(), h.getClusterName());
}
// wait for propagation, then
serviceOperatorV2.delete(namespaceId, serviceName);
Defensive patterns

Strategy: validation

Validate before calling

ServiceInfo info = serviceStorage.getData(service);
if (!info.getHosts().isEmpty()) {
    // drain instances first
    for (Instance h : info.getHosts()) {
        instanceOperator.deregisterInstance(namespaceId, group, name,
            h.getIp(), h.getPort(), h.getClusterName());
    }
    // wait for propagation
}
serviceOperatorV2.delete(service);

Try / catch

try {
    serviceOperatorV2.delete(service);
} catch (NacosApiException e) {
    if (e.getErrCode() == NacosException.INVALID_PARAM
            && ErrorCode.SERVICE_DELETE_FAILURE.getCode() == 21009) {
        // service still has instances — drain and retry
    } else throw e;
}

Prevention

When it happens

Trigger: Calling delete-service while one or more instances are still registered under the service. The instances may be ephemeral (still being heartbeated by clients) or persistent (stored in the CP log). Even unhealthy instances count as long as they are in the host list.

Common situations: Decommissioning a service without first deregistering all instances. Clients are still running and re-registering ephemeral instances faster than they can be removed. Persistent instances remain in the Raft log. A stale client keeps the instance alive via heartbeats.

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/469c0104a4546d90. Report an issue: GitHub.