alibaba/nacos · error · IllegalArgumentException

Duplicate runtime Endpoint: {endpointKey}

Error message

Duplicate runtime Endpoint: {endpointKey}

What it means

Thrown while validating a runtime Endpoint snapshot (RuntimeEndpointSnapshot) when two snapshot items resolve to the same EndpointNaturalKey across the whole snapshot. Unlike declared endpoints (per-CallInterface), runtime uniqueness is enforced across all items of one snapshot. The key is (namespaceId, agentName, protocol, normalizedHost, effectivePort, transport), so URIs that differ only cosmetically collide.

Source

Thrown at api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java:559

        for (Endpoint endpoint : endpoints) {
            validateEndpoint(endpoint);
            EndpointNaturalKey key = EndpointNaturalKey.of(namespaceId, agentName, protocol,
                endpoint);
            if (!endpointKeys.add(key)) {
                throw new IllegalArgumentException("Duplicate declared Endpoint: " + key);
            }
        }
    }
    
    private static void validateRuntimeEndpointSnapshotItem(RuntimeEndpointSnapshot snapshot,
        AgentVersion selectedVersion, RuntimeEndpointSnapshotItem item,
        Set<EndpointNaturalKey> endpointKeys) {
        requireNonNull(item, "runtimeEndpointSnapshot item");
        validateEndpoint(item.getEndpoint());
        EndpointNaturalKey endpointKey = EndpointNaturalKey.of(snapshot.getNamespaceId(),
            snapshot.getAgentName(), snapshot.getProtocol(), item.getEndpoint());
        if (!endpointKeys.add(endpointKey)) {
            throw new IllegalArgumentException("Duplicate runtime Endpoint: " + endpointKey);
        }
        
        List<RuntimeVersionBinding> bindings = item.getBindings();
        requireNonNull(bindings, "runtimeEndpointSnapshot.bindings");
        if (bindings.isEmpty()) {
            throw new IllegalArgumentException("Runtime Endpoint bindings must not be empty");
        }
        Set<String> bindingKeys = new HashSet<String>();
        for (RuntimeVersionBinding binding : bindings) {
            validateRuntimeVersionBinding(binding, selectedVersion, bindingKeys);
        }
        requireNonNull(item.getState(), "runtime Endpoint state");
        requireNonNull(item.getEnabled(), "runtime Endpoint enabled");
        requireNonNull(item.getHealthy(), "runtime Endpoint healthy");
        validateRuntimeEndpointState(item);
        validateEpochMillis(item.getLastUpdatedTime(), "lastUpdatedTime");
    }
    

View on GitHub (pinned to 9b989acdf1)

Solutions

  1. Dedup snapshot items by the canonical (host, port, transport) tuple; the message prints the colliding key as namespaceId/agentName/protocol/host:port/transport.
  2. Merge health, bindings, enabled, and state into a single RuntimeEndpointSnapshotItem for that address rather than emitting two.
  3. If you need the same host on two distinct transports (e.g. grpc vs http), ensure the transport field differs — that is part of the natural key.
  4. Pre-compute EndpointNaturalKey.of(...) for each item client-side to detect collisions before push.

Example fix

// before
itemA.setEndpoint(uri("https://svc:443")); itemA.setState(AVAILABLE);
itemB.setEndpoint(uri("https://svc:443")); itemB.setState(DISABLED); // duplicate key
// after
itemA.setEndpoint(uri("https://svc:443")); // single item, pick one authoritative state
Defensive patterns

Strategy: validation

Validate before calling

import com.alibaba.nacos.api.ai.utils.EndpointNaturalKey;
import java.util.HashSet;
import java.util.Set;

Set<EndpointNaturalKey> seen = new HashSet<>();
for (RuntimeEndpointSnapshotItem it : snapshot.getItems()) {
    EndpointNaturalKey k = EndpointNaturalKey.of(
        snapshot.getNamespaceId(), snapshot.getAgentName(),
        snapshot.getProtocol(), it.getEndpoint());
    if (!seen.add(k)) {
        throw new IllegalStateException("duplicate runtime endpoint: " + k);
    }
}

Type guard

static boolean noRuntimeDuplicates(RuntimeEndpointSnapshot snap) {
    Set<EndpointNaturalKey> s = new HashSet<>();
    for (RuntimeEndpointSnapshotItem it : snap.getItems()) {
        if (!s.add(EndpointNaturalKey.of(snap.getNamespaceId(), snap.getAgentName(),
                snap.getProtocol(), it.getEndpoint()))) return false;
    }
    return true;
}

Try / catch

try {
    AgentModelValidator.validateRuntimeEndpointSnapshot(snapshot);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Duplicate runtime Endpoint")) {
        // merge the two items' health/bindings/state into one
    } else throw e;
}

Prevention

When it happens

Trigger: validateRuntimeEndpointSnapshot iterates snapshot.getItems(), passing one shared endpointKeys set into validateRuntimeEndpointSnapshotItem, which calls EndpointNaturalKey.of(snapshot.getNamespaceId(), snapshot.getAgentName(), snapshot.getProtocol(), item.getEndpoint()). The second item with an already-seen key triggers this. Reached via AgentModelValidator.validateRuntimeEndpointSnapshot, typically invoked by the runtime management / RAD (Remote Agent Discovery) push path.

Common situations: A runtime agent reporting the same backend address twice with different health/bindings/state (those do not affect identity); an aggregator merging snapshots from two sub-agents without dedup; URI case/default-port collisions such as 'https://H' and 'https://h:443'; switching a host between IPv4 and a hostname that resolve to the same effective address but you intended distinct entries.

Related errors


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