grpc/grpc-java · error · IllegalArgumentException

Invalid percent-encoding at index

Error message

Invalid percent-encoding at index ${i} of ${what}: ${s}

What it means

Thrown during percent-decoding when a '%' character is found without two following characters, i.e. a percent-escape is truncated at the end of the string. The library strictly validates percent-encoding of URI components rather than tolerating bare '%' characters.

Solutions

  1. Percent-encode any literal '%' as '%25' before passing the value
  2. Ensure every '%' in the string is followed by exactly two hex digits
  3. Use a proper URL encoder (java.net.URLEncoder / component encoder) instead of manual string building
  4. If the value was truncated, fix the truncation source rather than the parser

Example fix

// before
builder.setPath("/files/100% done/report");
// after
String encoded = URLEncoder.encode("/files/100% done/report", StandardCharsets.UTF_8).replace("%2F", "/");
builder.setPath(encoded); // '%25' for the literal percent
Defensive patterns

Strategy: validation

Validate before calling

static boolean isWellFormedPercentEncoding(String s) {
  for (int i = 0; i < s.length(); i++) {
    if (s.charAt(i) == '%' && (i + 2 >= s.length()
        || Character.digit(s.charAt(i + 1), 16) == -1
        || Character.digit(s.charAt(i + 2), 16) == -1)) return false;
  }
  return true;
}

Try / catch

try { builder.setPath(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Value contains a literal '%' or truncated escape: " + value, e); }

Prevention

When it happens

Trigger: Passing a string containing a literal '%' not intended as an escape (e.g. '50% off') to a Uri component setter that validates percent-encoding, or a URI ending in '%', '%5', or '%2'.

Common situations: Double-encoding mistakes where '%' was inserted by concatenation; logging templates or user strings pasted into a path/query; truncated URIs cut off mid-escape.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at api/src/main/java/io/grpc/Uri.java:1057

   *
   * @throws IllegalArgumentException if 's' contains characters out of range or invalid percent
   *     encoding sequences.
   */
  public static ByteBuffer percentDecode(CharSequence s) {
    // This is large enough because each input character needs *at most* one byte of output.
    ByteBuffer outBuf = ByteBuffer.allocate(s.length());
    percentDecode(s, "input", null, outBuf);
    outBuf.flip();
    return outBuf;
  }

  private static void percentDecode(
      CharSequence s, String what, BitSet allowedChars, ByteBuffer outBuf) {
    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if (c == '%') {
        if (i + 2 >= s.length()) {
          throw new IllegalArgumentException(
              "Invalid percent-encoding at index " + i + " of " + what + ": " + s);
        }
        int h1 = Character.digit(s.charAt(i + 1), 16);
        int h2 = Character.digit(s.charAt(i + 2), 16);
        if (h1 == -1 || h2 == -1) {
          throw new IllegalArgumentException(
              "Invalid hex digit in " + what + " at index " + i + " of: " + s);
        }
        if (outBuf != null) {
          outBuf.put((byte) (h1 << 4 | h2));
        }
        i += 2;
      } else if (allowedChars == null || allowedChars.get(c)) {
        if (outBuf != null) {
          outBuf.put((byte) c);
        }
      } else {
        throw new IllegalArgumentException("Invalid character in " + what + " at index " + i);

View on GitHub (pinned to 64daddc1f3)