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
- 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
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
- 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
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
- Has authority -- Non-empty path must start with '/'
- Invalid character in scheme at index
- Invalid character in
- Invalid hex digit in
- Invalid host or port:
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)