grpc/grpc-java · error · GrpcServiceParseException

Missing or empty access token in call credentials.

Error message

Missing or empty access token in call credentials.

What it means

callCredsFromProto unpacks an AccessTokenCredentials plugin and requires a non-empty token string; when getToken() is empty it throws GrpcServiceParseException "Missing or empty access token in call credentials." Otherwise it wraps the token in OAuth2Credentials/SecurityAwareAccessTokenCredentials for call auth.

Solutions

  1. Populate the access_token field in the AccessTokenCredentials plugin before channel construction
  2. Fix the source of the token (env var, file, token agent) so a real value is injected into the config
  3. Switch to per-RPC or workload-identity call credentials that fetch tokens dynamically instead of embedding a static token

Example fix

// before
{"access_token": {"token": ""}}
// after
{"access_token": {"token": "ya29.<real-token>"}}
Defensive patterns

Strategy: validation

Validate before calling

// Check the token is present before constructing the channel
AccessTokenCredentials atc = cred.unpack(AccessTokenCredentials.class);
if (atc == null || atc.getToken().isEmpty()) {
  throw new IllegalArgumentException("AccessTokenCredentials.token must be non-empty");
}

Type guard

boolean hasNonEmptyToken(Any cred) throws InvalidProtocolBufferException {
  return cred.is(AccessTokenCredentials.class)
    && !cred.unpack(AccessTokenCredentials.class).getToken().isEmpty();
}

Try / catch

try {
  channel = ManagedChannelBuilder.forTarget(target)
      .intercept(authInterceptor)
      .build();
} catch (GrpcServiceParseException e) {
  if (e.getMessage().contains("access token")) {
    // refresh/mint the token and rebuild credentials
  } else throw e;
}

Prevention

When it happens

Trigger: parsed encounters a call_credentials plugin of type AccessTokenCredentials whose token field is empty or absent; the empty-check runs right after cred.unpack(AccessTokenCredentials.class).

Common situations: Template/bootstrap generated with a placeholder access_token field never filled in; control plane delivered credentials before the token was minted; env var substitution failed leaving the token blank.

Related errors


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

Appendix: source

Thrown at xds/src/main/java/io/grpc/xds/GrpcServiceConfigParser.java:259

  private static ConfiguredChannelCredentials extractChannelCredentials(
      List<Any> channelCredentialPlugins) throws GrpcServiceParseException {
    for (Any cred : channelCredentialPlugins) {
      Optional<ConfiguredChannelCredentials> parsed = channelCredsFromProto(cred);
      if (parsed.isPresent()) {
        return parsed.get();
      }
    }
    throw new GrpcServiceParseException("No valid supported channel_credentials found");
  }

  private static Optional<CallCredentials> callCredsFromProto(Any cred)
      throws GrpcServiceParseException {
    if (cred.is(AccessTokenCredentials.class)) {
      try {
        AccessTokenCredentials accessToken = cred.unpack(AccessTokenCredentials.class);
        if (accessToken.getToken().isEmpty()) {
          throw new GrpcServiceParseException("Missing or empty access token in call credentials.");
        }
        return Optional
            .of(new SecurityAwareAccessTokenCredentials(MoreCallCredentials.from(OAuth2Credentials
                .create(new AccessToken(accessToken.getToken(), new Date(Long.MAX_VALUE))))));
      } catch (InvalidProtocolBufferException e) {
        throw new GrpcServiceParseException(
            "Failed to parse access token credentials: " + e.getMessage());
      }
    }
    return Optional.empty();
  }

  private static Optional<CallCredentials> extractCallCredentials(List<Any> callCredentialPlugins)
      throws GrpcServiceParseException {
    List<CallCredentials> creds = new ArrayList<>();
    for (Any cred : callCredentialPlugins) {
      Optional<CallCredentials> parsed = callCredsFromProto(cred);
      if (parsed.isPresent()) {

View on GitHub (pinned to 64daddc1f3)