grpc/grpc-java · error · UnsupportedOperationException

Restricted: shutdown() is not allowed

Error message

Restricted: shutdown() is not allowed

What it means

This is thrown by a restricted ExecutorService wrapper inside ManagedChannelImpl (the channel's internal executor view exposed to balancers/listeners). Its shutdown() is deliberately disabled because the executor's lifecycle is owned by the channel itself; shutting it down independently would break the channel.

Source

Thrown at core/src/main/java/io/grpc/internal/ManagedChannelImpl.java:2189

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
      return delegate.invokeAny(tasks, timeout, unit);
    }

    @Override
    public boolean isShutdown() {
      return delegate.isShutdown();
    }

    @Override
    public boolean isTerminated() {
      return delegate.isTerminated();
    }

    @Override
    public void shutdown() {
      throw new UnsupportedOperationException("Restricted: shutdown() is not allowed");
    }

    @Override
    public List<Runnable> shutdownNow() {
      throw new UnsupportedOperationException("Restricted: shutdownNow() is not allowed");
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
      return delegate.submit(task);
    }

    @Override
    public Future<?> submit(Runnable task) {
      return delegate.submit(task);
    }

    @Override

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Call ManagedChannel.shutdown() (or shutdownNow()) instead of shutting down the restricted executor.
  2. If you own the underlying executor, shut down the executor you passed to ManagedChannelImplBuilder.executor(...), not the wrapped one.
  3. Remove cleanup code that closes executors it did not create.

Example fix

// before
executorService.shutdown(); // wrapped channel executor
// after
channel.shutdown();
Defensive patterns

Strategy: try-catch

Validate before calling

if (executor instanceof io.grpc.internal.ManagedChannelImplAccessor) { /* restricted */ } // otherwise track ownership: only shut down executors you created

Try / catch

try {
  executor.shutdown();
} catch (UnsupportedOperationException e) {
  // channel-owned executor; shut down the channel instead
  channel.shutdown();
}

Prevention

When it happens

Trigger: Calling shutdown() on the ExecutorService obtained via channel internals (e.g. Untargetable/Restricted executor returned to LoadBalancer Helpers or via o.grpc.Context storage), instead of shutting down the channel.

Common situations: Custom LoadBalancer or ClientTransportFactory code holding a reference to the channel executor and cleaning it up in its own shutdown path; tests disposing executor references directly.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/7bcee2a4fc81ae5d. Report an issue: GitHub.