apache/dubbo · error · IllegalStateException

INTERNAL_ERROR

INTERNAL_ERROR

Error message

reject to route, because the invokers has changed.

What it means

Thrown by SingleRouterChain.route() when the router chain's internally stored invokers origin list is not the same instance (compared by reference identity `!=`) as the availableInvokers origin list passed into the call. The chain is bound to a specific invoker list when obtained via RouterChain.getSingleChain(...); if that binding is bypassed, stale, or changed concurrently, this IllegalStateException fires. It is an internal consistency guard (logged as INTERNAL_ERROR), not a user-config error.

Source

Thrown at dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/SingleRouterChain.java:147

    }

    public List<Router> getRouters() {
        return routers;
    }

    public StateRouter<T> getHeadStateRouter() {
        return headStateRouter;
    }

    public List<Invoker<T>> route(URL url, BitList<Invoker<T>> availableInvokers, Invocation invocation) {
        if (invokers.getOriginList() != availableInvokers.getOriginList()) {
            logger.error(
                    INTERNAL_ERROR,
                    "",
                    "Router's invoker size: " + invokers.getOriginList().size() + " Invocation's invoker size: "
                            + availableInvokers.getOriginList().size(),
                    "Reject to route, because the invokers has changed.");
            throw new IllegalStateException("reject to route, because the invokers has changed.");
        }
        if (RpcContext.getServiceContext().isNeedPrintRouterSnapshot()) {
            return routeAndPrint(url, availableInvokers, invocation);
        } else {
            return simpleRoute(url, availableInvokers, invocation);
        }
    }

    public List<Invoker<T>> routeAndPrint(URL url, BitList<Invoker<T>> availableInvokers, Invocation invocation) {
        RouterSnapshotNode<T> snapshot = buildRouterSnapshot(url, availableInvokers, invocation);
        logRouterSnapshot(url, invocation, snapshot);
        return snapshot.getChainOutputInvokers();
    }

    public List<Invoker<T>> simpleRoute(URL url, BitList<Invoker<T>> availableInvokers, Invocation invocation) {
        BitList<Invoker<T>> resultInvokers = availableInvokers.clone();

        // 1. route state router

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Always obtain the SingleRouterChain via routerChain.getSingleChain(consumerUrl, availableInvokers, invocation) immediately before routing, and pass the exact same availableInvokers into route() so the origin list references match.
  2. If you see this in production, capture the router snapshot (enable dubbo.rpc.router.snapshot) and check whether the registry is churning address lists extremely fast; reduce notification frequency or stabilize the registry.
  3. In custom directory/chain code, never reuse a stale SingleRouterChain reference across invoker refreshes — re-fetch getSingleChain each call.
  4. Upgrade Dubbo: this is an internal invariant violation; report it with full thread dumps if it reproduces on unmodified Dubbo.

Example fix

// before (buggy): chain obtained once and reused while invokers change
SingleRouterChain<T> chain = cachedChain;
chain.route(url, freshAvailableInvokers, invocation);

// after: obtain chain inline and pass matching invokers
SingleRouterChain<T> chain = routerChain.getSingleChain(consumerUrl, availableInvokers, invocation);
chain.route(url, availableInvokers, invocation);
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the chain and the invokers are obtained together so origin lists match.
SingleRouterChain<T> chain = routerChain.getSingleChain(consumerUrl, availableInvokers, invocation);
// pass the SAME availableInvokers instance to route()
if (chain.getInvokers().getOriginList() == availableInvokers.getOriginList()) {
    chain.route(url, availableInvokers, invocation);
} else {
    // re-fetch instead of routing with a mismatched list
    throw new IllegalStateException("stale chain; refetch via getSingleChain");
}

Try / catch

try {
    chain.route(url, availableInvokers, invocation);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("invokers has changed")) {
        // race during address churn: back off and let the directory re-stabilize, then retry once
        Thread.sleep(50);
        SingleRouterChain<T> fresh = routerChain.getSingleChain(consumerUrl, directory.list(invocation), invocation);
        fresh.route(url, fresh.getInvokers(), invocation);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling SingleRouterChain.route(url, availableInvokers, invocation) with a BitList whose getOriginList() returns a different object reference than the chain's own invokers field. This happens under a race where the chain's invokers are swapped (re-notified) between getSingleChain() and route(), or when custom code constructs/obtains a chain and passes a foreign BitList.

Common situations: Concurrency races during rapid provider address notifications (registry pushing new address lists) while a concurrent RPC is mid-route; custom RouterChain wiring or unit tests that feed a BitList not produced by the directory; a bug in a downstream fork or custom directory implementation that does not pass the same origin list instance.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/47ff1e6c17bb33c7. Report an issue: GitHub.