prestodb/presto · error · IllegalArgumentException
Invalid %s header: %s
Error message
Invalid %s header: %s
What it means
Thrown by parsePreparedStatementsHeaders when an entry of the X-Presto-Prepared-Statement header cannot be URL-decoded (name or SQL text), wrapping the decoder's IllegalArgumentException with the header name and cause. The header entries must be properly percent-encoded name=SQL pairs.
Source
Thrown at presto-plan-checker-router-plugin/src/main/java/com/facebook/presto/router/scheduler/HttpRequestSessionContext.java:384
throw new IllegalStateException(format(format, args));
}
}
private static Map<String, String> parsePreparedStatementsHeaders(Map<String, List<String>> headerMap, SqlParserOptions sqlParserOptions)
{
ImmutableMap.Builder<String, String> preparedStatements = ImmutableMap.builder();
for (String header : splitSessionHeader(headerMap.getOrDefault(PRESTO_PREPARED_STATEMENT, emptyList()))) {
List<String> nameValue = Splitter.on('=').limit(2).trimResults().splitToList(header);
assertRequest(nameValue.size() == 2, "Invalid %s header", PRESTO_PREPARED_STATEMENT);
String statementName;
String sqlString;
try {
statementName = urlDecode(nameValue.get(0));
sqlString = urlDecode(nameValue.get(1));
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException((format("Invalid %s header: %s", PRESTO_PREPARED_STATEMENT, e.getMessage())));
}
// Validate statement
SqlParser sqlParser = new SqlParser(sqlParserOptions);
try {
sqlParser.createStatement(sqlString, new ParsingOptions(AS_DOUBLE /* anything */));
}
catch (ParsingException e) {
throw new IllegalStateException(format("Invalid %s header: %s", PRESTO_PREPARED_STATEMENT, e.getMessage()));
}
preparedStatements.put(statementName, sqlString);
}
return preparedStatements.build();
}
private static Optional<TransactionId> parseTransactionId(String transactionId)
{View on GitHub (pinned to 55bb57d202)
Solutions
- URL-encode both the statement name and the SQL text before placing them in the X-Presto-Prepared-Statement header
- Use the official client/StatementClient code path which encodes the header automatically instead of hand-building it
- Inspect the wrapped IllegalArgumentException message to find the exact undecodable sequence
Example fix
// before header: SELECT * FROM t WHERE x LIKE 'a%' // after header: SELECT%20*%20FROM%20t%20WHERE%20x%20LIKE%20%27a%25%27
Defensive patterns
Strategy: validation
Validate before calling
String encoded = URLEncoder.encode(sql, StandardCharsets.UTF_8);
// verify it round-trips
if (!URLDecoder.decode(encoded, StandardCharsets.UTF_8).equals(sql)) { throw new IllegalStateException("Encoding mismatch"); } Try / catch
try { parsePreparedStatementsHeaders(headerMap, opts); }
catch (IllegalArgumentException e) { log.warn("Prepared statement header rejected: {}", e.getMessage()); return Response.status(400).build(); } Prevention
- Always URL-encode prepared statement names and SQL
- Prefer the official Presto client's header construction
- Watch for intermediaries rewriting or truncating header values
When it happens
Trigger: A prepared statement header value contains raw characters like '%', '=', or non-ASCII SQL text that was not percent-encoded, so urlDecode throws IllegalArgumentException for that entry.
Common situations: Clients building the header manually without URL-encoding the SQL string, double-encoding or truncation by intermediaries/proxies, pasting a statement containing a literal '%' into a config.
Related errors
- INVALID_PARAMETER_USAGE
- INVALID_ARGUMENTS
- setURL
- No value specified for parameter
- Batch prepared statement must be executed using executeBatch
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/0f51c8b3df877629.
Report an issue: GitHub.