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

  1. URL-encode both the statement name and the SQL text before placing them in the X-Presto-Prepared-Statement header
  2. Use the official client/StatementClient code path which encodes the header automatically instead of hand-building it
  3. 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

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


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/0f51c8b3df877629. Report an issue: GitHub.