apache/dubbo · error · RpcException

Local service for has not been exposed yet!

Error message

Local service for {getUrl().getServiceInterface()} has not been exposed yet!

What it means

Thrown by ScopeClusterInvoker.isInjvmExported when the call is forced to be local (scope=local, or the injvm protocol flag set / RpcContext local-invoke requested) but the service is NOT exported in the current JVM (isExported is false). It refuses to make a remote call because the configuration demanded local scope, yet no local exporter exists.

Solutions

  1. Expose the service locally in the same JVM (ensure the provider is configured and started, e.g. scope=local export) so an injvm exporter exists before the call.
  2. Remove the local-scope forcing (scope=local / injvm flag / setLocalInvoke(true)) to allow a normal remote invocation.
  3. Fix provider startup ordering so the injvm exporter is registered before the consumer issues a local-scoped call.
  4. Verify the service interface name in the message matches a service this JVM actually exposes.

Example fix

// before: consumer forces local scope but service not exported in JVM
<dubbo:reference interface="com.acme.Svc" scope="local"/>

// after: allow remote, or also expose locally
<dubbo:reference interface="com.acme.Svc" scope="remote"/>
<!-- plus, if local is desired: -->
<dubbo:service interface="com.acme.Svc" ref="svcImpl" scope="local"/>
Defensive patterns

Strategy: validation

Validate before calling

// Before issuing a local-scoped call, confirm a local exporter exists
if (!isLocalExporterPresent(url.getServiceInterface())) {
    throw new IllegalStateException("service not exported locally; cannot use scope=local");
}

Try / catch

try {
    return service.call(req);
} catch (RpcException e) {
    if (e.getMessage() != null && e.getMessage().endsWith("has not been exposed yet!")) {
        log.error("local scope forced but no injvm exporter; expose locally or use remote", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Url config scope=local (or LOCAL_PROTOCOL/injvm flag true) and the service was never injvm-exported in this JVM; or RpcContext.getServiceContext().setLocalInvoke(true) on a consumer whose provider side has not exposed the service locally; calling a service from a different application/module context that never registered an injvm exporter.

Common situations: Enabling scope=local on a pure consumer application that does not also expose the service in-process; provider failed to start/export so no local exporter was created; cross-module calls where the exporting module loaded later (startup ordering); misreading injvm vs scope semantics and forcing local on a remote-only service.

Related errors


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

Appendix: source

Thrown at dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/ScopeClusterInvoker.java:272

     */
    private boolean isInjvmExported() {
        Boolean localInvoke = RpcContext.getServiceContext().getLocalInvoke();
        boolean isExportedValue = isExported.get();
        boolean localOnce = (localInvoke != null && localInvoke);
        // Determine whether this call is local
        if (isExportedValue && localOnce) {
            return true;
        }

        // Determine whether this call is remote
        if (localInvoke != null && !localInvoke) {
            return false;
        }

        // When calling locally, determine whether it does not meet the requirements
        if (!isExportedValue && (isForceLocal() || localOnce)) {
            // If it's supposed to be exported to the local JVM ,but it's not, throw an exception
            throw new RpcException(
                    "Local service for " + getUrl().getServiceInterface() + " has not been exposed yet!");
        }

        return isExportedValue && injvmFlag;
    }

    private boolean isForceLocal() {
        return SCOPE_LOCAL.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY))
                || Boolean.TRUE.toString().equalsIgnoreCase(getUrl().getParameter(LOCAL_PROTOCOL));
    }

    /**
     * Creates a new Invoker for the current ScopeClusterInvoker and exports it to the local JVM.
     */
    private void createInjvmInvoker(Exporter<?> exporter) {
        if (injvmInvoker == null) {
            synchronized (createLock) {
                if (injvmInvoker == null) {

View on GitHub (pinned to 3a3043227f)