grpc/grpc-java · error · IllegalArgumentException

HeaderMatcher [] contains malformed safe regex pattern:

Error message

HeaderMatcher [] contains malformed safe regex pattern: 

What it means

MatcherParser.parseHeaderMatcher compiles the Envoy HeaderMatcher's safe_regex pattern with java.util.regex.Pattern.compile. If the regex is syntactically invalid (PatternSyntaxException), it wraps the failure into an IllegalArgumentException naming the header and the underlying regex error. The xDS config contained a regex this JVM cannot compile.

Solutions

  1. Fix the regex in the xDS management server config so it is valid java.util.regex syntax
  2. Test the pattern with Pattern.compile before deploying it to the control plane
  3. Read the embedded PatternSyntaxException message (it pinpoints index and problem)
  4. Avoid RE2/Go-only constructs; use Java-compatible equivalents

Example fix

// xDS config before
{"name":"x-tenant","safe_regex_match":{"regex":"(?P<tenant>.*)"}}
// after (named groups unsupported in older Java regex handling here)
{"name":"x-tenant","safe_regex_match":{"regex":".*"}}
Defensive patterns

Strategy: validation

Validate before calling

try { java.util.regex.Pattern.compile(regex); } catch (java.util.regex.PatternSyntaxException e) { /* reject before deploying to control plane */ }

Type guard

null

Try / catch

try { matcher = MatcherParser.parseHeaderMatcher(proto); }
catch (IllegalArgumentException e) { log.error("Bad header regex in route config", e); failConfigLoad(); }

Prevention

When it happens

Trigger: An LDS/RDS route configuration contains a header_match with safe_regex_match whose regex string fails java.util.regex compilation (e.g. invalid escapes, unbalanced groups, RE2-only syntax).

Common situations: Regexes authored for RE2/other engines (e.g. possessive quantifiers or \p{...} differences) pasted into Envoy config; hand-edited control-plane config with a typo; control plane emitting Go-regexp syntax not valid in Java.

Understand the failure class

Related errors


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

Appendix: source

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

  }

  /** Translates envoy proto HeaderMatcher to internal HeaderMatcher.*/
  public static Matchers.HeaderMatcher parseHeaderMatcher(
          io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
    switch (proto.getHeaderMatchSpecifierCase()) {
      case EXACT_MATCH:
        @SuppressWarnings("deprecation") // gRFC A63: support indefinitely
        String exactMatch = proto.getExactMatch();
        return Matchers.HeaderMatcher.forExactValue(
                        proto.getName(), exactMatch, proto.getInvertMatch());
      case SAFE_REGEX_MATCH:
        @SuppressWarnings("deprecation") // gRFC A63: support indefinitely
        String rawPattern = proto.getSafeRegexMatch().getRegex();
        Pattern safeRegExMatch;
        try {
          safeRegExMatch = Pattern.compile(rawPattern);
        } catch (PatternSyntaxException e) {
          throw new IllegalArgumentException(
                "HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: "
                        + e.getMessage());
        }
        return Matchers.HeaderMatcher.forSafeRegEx(
              proto.getName(), safeRegExMatch, proto.getInvertMatch());
      case RANGE_MATCH:
        Matchers.HeaderMatcher.Range rangeMatch = Matchers.HeaderMatcher.Range.create(
              proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd());
        return Matchers.HeaderMatcher.forRange(
              proto.getName(), rangeMatch, proto.getInvertMatch());
      case PRESENT_MATCH:
        return Matchers.HeaderMatcher.forPresent(
              proto.getName(), proto.getPresentMatch(), proto.getInvertMatch());
      case PREFIX_MATCH:
        @SuppressWarnings("deprecation") // gRFC A63: support indefinitely
        String prefixMatch = proto.getPrefixMatch();
        return Matchers.HeaderMatcher.forPrefix(
              proto.getName(), prefixMatch, proto.getInvertMatch());

View on GitHub (pinned to 64daddc1f3)