grpc/grpc-java · error · IllegalArgumentException
Invalid hex digit in
Error message
Invalid hex digit in ${what} at index ${i} of: ${s} What it means
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.
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
Example fix
// before
builder.setPath("/a%zzb");
// after
builder.setPath(URLEncoder.encode("/a%zzb", StandardCharsets.UTF_8)); // or correct escape to %2F etc. Defensive patterns
Strategy: validation
Validate before calling
static boolean hasValidHexEscapes(String s) {
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '%') {
if (i + 2 >= s.length()) return false;
if (Character.digit(s.charAt(i + 1), 16) == -1 || Character.digit(s.charAt(i + 2), 16) == -1) return false;
i += 2;
}
}
return true;
} Try / catch
try { builder.setQuery(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Malformed percent-escape in: " + value, e); } Prevention
- 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
When it happens
Trigger: Passing a component string containing malformed escapes like '%GG', '%2x', or '%zz' to a Uri builder setter that validates percent-encoding.
Common situations: 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.
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
- Invalid percent-encoding at index
- cannot create a NameResolver for
- Could not find a NameResolverProvider for
- Has authority -- Non-empty path must start with '/'
- Invalid authority:
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/9f7eebbf06bdf3b3.
Report an issue: GitHub.
Appendix: source
Thrown at api/src/main/java/io/grpc/Uri.java:1063
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);
}
}
}
@Nullable
private static String percentDecodeAssumedUtf8(@Nullable String s) {View on GitHub (pinned to 64daddc1f3)