grpc/grpc-java · error · IllegalArgumentException

StringMatcher (match_pattern) must be non-empty

Error message

StringMatcher  (match_pattern) must be non-empty

What it means

checkNonEmpty rejects StringMatcher values that are empty (e.g. an empty contains or exact value) with an IllegalArgumentException of the form 'StringMatcher <name> (match_pattern) must be non-empty'. The parser treats an empty match value as invalid config because it would match nothing meaningful.

Solutions

  1. Populate the match value with a non-empty string in the xDS config
  2. Reject empty match values at the management server before distribution
  3. Check for templating/config-generation bugs that emit empty values
  4. If an empty match is intentional, use a different pattern type (e.g. prefix) if semantics allow

Example fix

// before
{"string_match":{"contains":""}}
// after
{"string_match":{"contains":"session="}}
Defensive patterns

Strategy: validation

Validate before calling

if (value == null || value.isEmpty()) { reject(name + " must be non-empty"); }

Type guard

null

Try / catch

try { matcher = MatcherParser.parseHeaderMatcher(proto); }
catch (IllegalArgumentException e) { if (e.getMessage().contains("must be non-empty")) { fixConfigEntry(); } }

Prevention

When it happens

Trigger: parseStringMatcher calls checkNonEmpty (e.g. for contains) and the proto field is set but holds an empty string.

Common situations: contains:"" or exact:"" in Envoy config generated by tooling; a field defaulting to empty after stripping whitespace; templating bug leaving a placeholder unfilled.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at xds/src/main/java/io/grpc/xds/internal/MatcherParser.java:139

      case SUFFIX:
        return Matchers.StringMatcher.forSuffix(
            checkNonEmpty(proto.getSuffix(), "suffix"), proto.getIgnoreCase());
      case SAFE_REGEX:
        String regex = checkNonEmpty(proto.getSafeRegex().getRegex(), "regex");
        return Matchers.StringMatcher.forSafeRegEx(Pattern.compile(regex));
      case CONTAINS:
        return Matchers.StringMatcher.forContains(
            checkNonEmpty(proto.getContains(), "contains"), proto.getIgnoreCase());
      case MATCHPATTERN_NOT_SET:
      default:
        throw new IllegalArgumentException(
            "Unknown StringMatcher match pattern: " + proto.getMatchPatternCase());
    }
  }

  private static String checkNonEmpty(String value, String name) {
    if (value.isEmpty()) {
      throw new IllegalArgumentException("StringMatcher " + name 
          + " (match_pattern) must be non-empty");
    }
    return value;
  }

  /** Translates envoy proto FractionalPercent to internal FractionMatcher. */
  public static Matchers.FractionMatcher parseFractionMatcher(
      io.envoyproxy.envoy.type.v3.FractionalPercent proto) {
    int denominator;
    switch (proto.getDenominator()) {
      case HUNDRED:
        denominator = 100;
        break;
      case TEN_THOUSAND:
        denominator = 10_000;
        break;
      case MILLION:
        denominator = 1_000_000;

View on GitHub (pinned to 64daddc1f3)