apache/beam · error · IllegalStateException

RateLimitServiceStub is null

Error message

RateLimitServiceStub is null

What it means

Inside EnvoyRateLimiterFactory.fetchTokens(), after init() the gRPC RateLimitServiceBlockingStub field is read into a local and checked for null; if the channel was never established (or init failed silently), an IllegalStateException("RateLimitServiceStub is null") is thrown rather than sending a request on a null stub.

Source

Thrown at sdks/java/io/components/src/main/java/org/apache/beam/sdk/io/components/ratelimiter/EnvoyRateLimiterFactory.java:136

      return true;
    }
    if (!(context instanceof EnvoyRateLimiterContext)) {
      throw new IllegalArgumentException(
          "EnvoyRateLimiterFactory requires EnvoyRateLimiterContext, got: "
              + context.getClass().getName());
    }
    checkArgument(permits >= 0, "Permits must be non-negative");
    EnvoyRateLimiterContext envoyContext = (EnvoyRateLimiterContext) context;
    return fetchTokens(envoyContext, permits);
  }

  private boolean fetchTokens(EnvoyRateLimiterContext context, int tokens)
      throws IOException, InterruptedException {

    init();
    RateLimitServiceGrpc.RateLimitServiceBlockingStub currentStub = stub;
    if (currentStub == null) {
      throw new IllegalStateException("RateLimitServiceStub is null");
    }

    Map<String, String> descriptors = context.getDescriptors();
    RateLimitDescriptor.Builder descriptorBuilder = RateLimitDescriptor.newBuilder();

    for (Map.Entry<String, String> entry : descriptors.entrySet()) {
      descriptorBuilder.addEntries(
          RateLimitDescriptor.Entry.newBuilder()
              .setKey(entry.getKey())
              .setValue(entry.getValue())
              .build());
    }

    RateLimitRequest request =
        RateLimitRequest.newBuilder()
            .setDomain(context.getDomain())
            .setHitsAddend(tokens)
            .addDescriptors(descriptorBuilder.build())

View on GitHub (pinned to 12126d8942)

Solutions

  1. Ensure the Envoy rate-limit service address is reachable and that init() runs (e.g. in DoFn.setup()) before any allow() call.
  2. Fix the constructor wiring so a valid RateLimitServiceGrpc stub/channel is passed to the factory.
  3. Retry after init — if init() is lazy/async, make it synchronous or wait for completion before fetching tokens.

Example fix

// before
EnvoyRateLimiterFactory factory = new EnvoyRateLimiterFactory(config, null); // stub never set
// after
ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
RateLimitServiceGrpc.RateLimitServiceBlockingStub stub = RateLimitServiceGrpc.newBlockingStub(channel);
EnvoyRateLimiterFactory factory = new EnvoyRateLimiterFactory(config, stub);
Defensive patterns

Strategy: validation

Validate before calling

if (stub == null) {
  throw new IllegalStateException("initialize EnvoyRateLimiterFactory with a non-null RateLimitService stub before allow()");
}

Try / catch

try {
  allowed = factory.allow(ctx, permits);
} catch (IllegalStateException e) {
  // re-init the factory/channel, then retry once
  factory.init();
  allowed = factory.allow(ctx, permits);
}

Prevention

When it happens

Trigger: Calling allow() before the factory's asynchronous init() completed, init() failed to create the channel/stub (e.g. bad target address, TLS setup failure), or the stub field was never set because the factory was constructed without a stub.

Common situations: Envoy rate-limit service unreachable or hostname misconfigured at pipeline startup, race between construction and first allow() call in a multithreaded DoFn setup, or missing gRPC dependencies preventing channel creation.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/6ee0a32a6ef3649f. Report an issue: GitHub.