{"record":{"id":"9f7eebbf06bdf3b3","repo":"grpc/grpc-java","slug":"invalid-hex-digit-in-what-at-index-i-of-s","errorCode":null,"errorMessage":"Invalid hex digit in ${what} at index ${i} of: ${s}","messagePattern":"Invalid hex digit in (.+?) at index (.+?) of: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/io/grpc/Uri.java","lineNumber":1063,"sourceCode":"    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);\n      }\n    }\n  }\n\n  @Nullable\n  private static String percentDecodeAssumedUtf8(@Nullable String s) {","sourceCodeStart":1045,"sourceCodeEnd":1081,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/api/src/main/java/io/grpc/Uri.java#L1045-L1081","documentation":"Thrown during percent-decoding when a '%' escape is followed by characters that are not valid hexadecimal digits, e.g. '%zz' or '%2G'. The library uses Character.digit(c, 16) and rejects any escape whose two trailing chars are not 0-9/a-f/A-F.","triggerScenarios":"Passing a component string containing malformed escapes like '%GG', '%2x', or '%zz' to a Uri builder setter that validates percent-encoding.","commonSituations":"Hand-crafted or partially URL-encoded strings; corruption from naive string truncation/repair; encoding bugs that uppercase/lowercase-mangle hex or inject non-hex placeholders.","solutions":["Fix or remove the malformed escape; every '%' must be followed by two hex digits","Re-encode the value with a standard URL encoder instead of manual escapes","Decode with a lenient decoder first to inspect what the string actually contains","Escape literal '%' as '%25' if it was never meant as an escape"],"exampleFix":"// before\nbuilder.setPath(\"/a%zzb\");\n// after\nbuilder.setPath(URLEncoder.encode(\"/a%zzb\", StandardCharsets.UTF_8)); // or correct escape to %2F etc.","handlingStrategy":"validation","validationCode":"static boolean hasValidHexEscapes(String s) {\n  for (int i = 0; i < s.length(); i++) {\n    if (s.charAt(i) == '%') {\n      if (i + 2 >= s.length()) return false;\n      if (Character.digit(s.charAt(i + 1), 16) == -1 || Character.digit(s.charAt(i + 2), 16) == -1) return false;\n      i += 2;\n    }\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try { builder.setQuery(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(\"Malformed percent-escape in: \" + value, e); }","preventionTips":["Re-encode malformed strings with a standard encoder instead of patching escapes","Detect %zz-style escapes before passing values","Keep hex escapes lowercase/uppercase consistently","Escape literal % as %25"],"tags":["uri","percent-encoding","hex"],"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"}