grpc/grpc-java · error · VerifyException

Status code is not valid

Error message

Status code ${status} is not valid

What it means

ServiceConfigUtil.getStatusCodesFromList parses the retryable status codes list from a gRPC service config. Each entry must be either an integer equal to a valid Status code value or a string naming a Status.Code enum constant. When an integer entry does not match any real status code value, a VerifyException is thrown during config parsing.

Solutions

  1. Check each numeric entry in retryableStatusCodes against the gRPC Status code value table (e.g. 0=OK, 3=INVALID_ARGUMENT, 8=RESOURCE_EXHAUSTED, 14=UNAVAILABLE, 16=UNAUTHENTICATED)
  2. Prefer string names like "UNAVAILABLE" over numeric values to eliminate value-table mistakes
  3. Remove or correct the offending entry and redeploy the service config
  4. Validate the full service config with the grpc service config schema/validator before pushing it

Example fix

// before
"retryableStatusCodes": [14, 99]
// after
"retryableStatusCodes": ["UNAVAILABLE", "RESOURCE_EXHAUSTED"]
Defensive patterns

Strategy: validation

Validate before calling

// Validate before trusting config
for (Object status : statusCodes) {
  if (status instanceof Integer) {
    int v = (Integer) status;
    if (io.grpc.Status.fromCodeValue(v) == io.grpc.Status.UNKNOWN && v != io.grpc.Status.UNKNOWN.getCode().value()) {
      throw new IllegalArgumentException("Not a gRPC status code value: " + v);
    }
  } else if (status instanceof String) {
    io.grpc.Status.Code.valueOf((String) status); // throws if invalid
  } else {
    throw new IllegalArgumentException("Bad type: " + status.getClass());
  }
}

Try / catch

try {
  codes = ServiceConfigUtil.getListOfStatusCodesAsSet(rawList);
} catch (VerifyException e) {
  log.error("Bad retryableStatusCodes in service config: " + e.getMessage());
  throw new ConfigInvalidException(e);
}

Prevention

When it happens

Trigger: A service config's retryPolicy.retryableStatusCodes (or similar) list contains a numeric entry whose value does not correspond to a gRPC status code value (e.g. 7 corresponds to PERMISSION_DENIED, but 99 or 1234 does not). Reached via getListOfStatusCodesAsSet during service config resolution.

Common situations: Hand-written or tool-generated service config JSON with a typo in a numeric status code value; a config generated against a newer/different status code table; copy-pasted values from HTTP status codes instead of gRPC status codes.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at core/src/main/java/io/grpc/internal/ServiceConfigUtil.java:170

    }
    return getStatusCodesFromList(statuses);
  }

  private static Set<Status.Code> getStatusCodesFromList(List<?> statuses) {
    EnumSet<Status.Code> codes = EnumSet.noneOf(Status.Code.class);
    for (Object status : statuses) {
      Status.Code code;
      if (status instanceof Double) {
        Double statusD = (Double) status;
        int codeValue = statusD.intValue();
        verify((double) codeValue == statusD, "Status code %s is not integral", status);
        code = Status.fromCodeValue(codeValue).getCode();
        verify(code.value() == statusD.intValue(), "Status code %s is not valid", status);
      } else if (status instanceof String) {
        try {
          code = Status.Code.valueOf((String) status);
        } catch (IllegalArgumentException iae) {
          throw new VerifyException("Status code " + status + " is not valid", iae);
        }
      } else {
        throw new VerifyException(
            "Can not convert status code " + status + " to Status.Code, because its type is "
                + status.getClass());
      }
      codes.add(code);
    }
    return Collections.unmodifiableSet(codes);
  }

  static Set<Status.Code> getRetryableStatusCodesFromRetryPolicy(Map<String, ?> retryPolicy) {
    String retryableStatusCodesKey = "retryableStatusCodes";
    Set<Status.Code> codes = getListOfStatusCodesAsSet(retryPolicy, retryableStatusCodesKey);
    verify(codes != null, "%s is required in retry policy", retryableStatusCodesKey);
    verify(!codes.contains(Status.Code.OK), "%s must not contain OK", retryableStatusCodesKey);
    return codes;
  }

View on GitHub (pinned to 64daddc1f3)