grpc/grpc-java · error · IllegalArgumentException

CEL expression must evaluate to string, got: ${ast.getResult

Error message

CEL expression must evaluate to string, got: ${ast.getResultType()}

What it means

CelStringExtractor.compile() requires the CEL AST's result type to be STRING (or DYN), because the extractor evaluates the expression to produce a header/value string for downstream matching. Any other result type is rejected at compile time with IllegalArgumentException naming the actual type.

Source

Thrown at xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java:46

 */
final class CelStringExtractor {
  private final CelRuntime.Program program;
  @Nullable
  private final String defaultValue;

  private CelStringExtractor(CelRuntime.Program program, @Nullable String defaultValue) {
    this.program = program;
    this.defaultValue = defaultValue;
  }

  /**
   * Compiles the AST into a CelStringExtractor with an optional default value.
   * Throws an Exception if evaluation fails during compilation setup.
   */
  static CelStringExtractor compile(CelAbstractSyntaxTree ast, @Nullable String defaultValue)
      throws CelEvaluationException {
    if (ast.getResultType() != SimpleType.STRING && ast.getResultType() != SimpleType.DYN) {
      throw new IllegalArgumentException(
          "CEL expression must evaluate to string, got: " + ast.getResultType());
    }
    CelCommon.checkAllowedReferences(ast);
    CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast);
    return new CelStringExtractor(program, defaultValue);
  }

  /**
   * Compiles the AST into a CelStringExtractor with no default value.
   * Throws an Exception if evaluation fails during compilation setup.
   */
  static CelStringExtractor compile(CelAbstractSyntaxTree ast)
      throws CelEvaluationException {
    return compile(ast, null);
  }

  /**
   * Evaluates the CEL expression and returns the string result.

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Change the expression so it evaluates to a string, e.g. request.headers['x-user-id'] or string(tls.san).
  2. Wrap non-string values with CEL conversions: string(...) for ints, or a ternary producing string branches.
  3. If a boolean predicate is actually needed, use CelMatcher.compile() instead of CelStringExtractor.compile().

Example fix

// before
compile(compiler.compile("request.host == 'edge'"), null); // bool
// after
compile(compiler.compile("request.headers['x-user-id']"), "anonymous");
Defensive patterns

Strategy: validation

Validate before calling

if (ast.getResultType() != SimpleType.STRING && ast.getResultType() != SimpleType.DYN) {
  throw new IllegalArgumentException("CEL expr must be string, got: " + ast.getResultType());
}

Type guard

boolean isStringAst(CelAbstractSyntaxTree ast) {
  return ast.getResultType() == SimpleType.STRING || ast.getResultType() == SimpleType.DYN;
}

Try / catch

try { return CelStringExtractor.compile(ast, def); }
catch (IllegalArgumentException e) { logger.warn("non-string CEL expr", e); return null; }

Prevention

When it happens

Trigger: Calling CelStringExtractor.compile(ast, defaultValue) with an AST whose getResultType() is, e.g., SimpleType.BOOL or INT — typically a predicate expression like 'a == b' passed where a value expression like 'request.headers["x-user"]' is expected.

Common situations: Copy-pasting a boolean matcher expression into the extractor position; control plane generating int/bool-typed attribute expressions; schema migration changing the expression's inferred type.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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