{"record":{"id":"f9bf4761c8309b04","repo":"grpc/grpc-java","slug":"invalid-percent-encoding-at-index-i-of-what","errorCode":null,"errorMessage":"Invalid percent-encoding at index ${i} of ${what}: ${s}","messagePattern":"Invalid percent-encoding at index (.+?) of (.+?): (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/io/grpc/Uri.java","lineNumber":1057,"sourceCode":"   *\n   * @throws IllegalArgumentException if 's' contains characters out of range or invalid percent\n   *     encoding sequences.\n   */\n  public static ByteBuffer percentDecode(CharSequence s) {\n    // This is large enough because each input character needs *at most* one byte of output.\n    ByteBuffer outBuf = ByteBuffer.allocate(s.length());\n    percentDecode(s, \"input\", null, outBuf);\n    outBuf.flip();\n    return outBuf;\n  }\n\n  private static void percentDecode(\n      CharSequence s, String what, BitSet allowedChars, ByteBuffer outBuf) {\n    for (int i = 0; i < s.length(); i++) {\n      char c = s.charAt(i);\n      if (c == '%') {\n        if (i + 2 >= s.length()) {\n          throw new IllegalArgumentException(\n              \"Invalid percent-encoding at index \" + i + \" of \" + what + \": \" + s);\n        }\n        int h1 = Character.digit(s.charAt(i + 1), 16);\n        int h2 = Character.digit(s.charAt(i + 2), 16);\n        if (h1 == -1 || h2 == -1) {\n          throw new IllegalArgumentException(\n              \"Invalid hex digit in \" + what + \" at index \" + i + \" of: \" + s);\n        }\n        if (outBuf != null) {\n          outBuf.put((byte) (h1 << 4 | h2));\n        }\n        i += 2;\n      } else if (allowedChars == null || allowedChars.get(c)) {\n        if (outBuf != null) {\n          outBuf.put((byte) c);\n        }\n      } else {\n        throw new IllegalArgumentException(\"Invalid character in \" + what + \" at index \" + i);","sourceCodeStart":1039,"sourceCodeEnd":1075,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/api/src/main/java/io/grpc/Uri.java#L1039-L1075","documentation":"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.","triggerScenarios":"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'.","commonSituations":"Double-encoding mistakes where '%' was inserted by concatenation; logging templates or user strings pasted into a path/query; truncated URIs cut off mid-escape.","solutions":["Percent-encode any literal '%' as '%25' before passing the value","Ensure every '%' in the string is followed by exactly two hex digits","Use a proper URL encoder (java.net.URLEncoder / component encoder) instead of manual string building","If the value was truncated, fix the truncation source rather than the parser"],"exampleFix":"// before\nbuilder.setPath(\"/files/100% done/report\");\n// after\nString encoded = URLEncoder.encode(\"/files/100% done/report\", StandardCharsets.UTF_8).replace(\"%2F\", \"/\");\nbuilder.setPath(encoded); // '%25' for the literal percent","handlingStrategy":"validation","validationCode":"static boolean isWellFormedPercentEncoding(String s) {\n  for (int i = 0; i < s.length(); i++) {\n    if (s.charAt(i) == '%' && (i + 2 >= s.length()\n        || Character.digit(s.charAt(i + 1), 16) == -1\n        || Character.digit(s.charAt(i + 2), 16) == -1)) return false;\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try { builder.setPath(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(\"Value contains a literal '%' or truncated escape: \" + value, e); }","preventionTips":["Always escape literal % as %25","Encode with URLEncoder/URI component encoders, not manual concatenation","Never truncate URI strings mid-escape","Check strings from logs/templates for stray % before reuse"],"tags":["uri","percent-encoding","validation"],"backgroundTag":"invalid-url-format","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}