apache/druid · error · IllegalArgumentException

Function[ ] substring must be a string literal

Error message

Function[%s] substring must be a string literal

What it means

ContainsExpr (contains/icontains string functions) requires the substring argument to be a string literal so it can compile to a constant predicate. If the search expression is not a literal string (e.g. a column reference, concatenated expression, or null), the function cannot be evaluated statically and Druid throws this IAE.

Solutions

  1. Pass a quoted string literal: contains(col, 'substr')
  2. Use SQL with LIKE or a filter (e.g. BoundFilter/like filter) for dynamic patterns
  3. Compute the constant in application code before issuing the query

Example fix

// before
contains(channel, otherColumn)
// after
contains(channel, 'news')
Defensive patterns

Strategy: validation

Validate before calling

if (!(searchArg instanceof String)) {
  throw new IllegalArgumentException("contains() substring must be a string literal");
}

Type guard

boolean isLiteralString = expr != null && expr.isLiteral() && expr.getLiteralValue() instanceof String;

Try / catch

try {
  Expr parsed = Parser.parse(exprString);
} catch (IAE e) {
  if (e.getMessage().contains("must be a string literal")) { /* use like filter instead */ }
}

Prevention

When it happens

Trigger: Calling contains(...)/icontains(...) with a column or computed expression as the second argument instead of a quoted string literal.

Common situations: Trying to search with a value from another column; building SQL LIKE-like dynamic queries through the Druid expression API.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/264a8449a048f684. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/expression/ContainsExpr.java:89

    if (s == null) {
      // same behavior as regexp_like.
      return ExprEval.ofLong(null);
    } else {
      final boolean doesContain = searchFunction.apply(s);
      return ExprEval.ofLongBoolean(doesContain);
    }
  }

  @Override
  public ExpressionType getOutputType(InputBindingInspector inspector)
  {
    return ExpressionType.LONG;
  }

  private static String getSearchString(Expr searchExpr, String functioName)
  {
    if (!ExprUtils.isStringLiteral(searchExpr)) {
      throw new IAE("Function[%s] substring must be a string literal", functioName);
    }
    return StringUtils.nullToEmptyNonDruidDataString((String) searchExpr.getLiteralValue());
  }

  private static Function<String, Boolean> createFunction(String searchString, boolean caseSensitive)
  {
    if (caseSensitive) {
      return s -> s.contains(searchString);
    } else {
      return s -> org.apache.commons.lang3.StringUtils.containsIgnoreCase(s, searchString);
    }
  }
}

View on GitHub (pinned to 9b90983fd2)