grpc/grpc-java · error · IllegalArgumentException
Malformed input
Error message
Malformed input
What it means
Thrown when converting a string to the URI's internal percent-encoded form, the UTF-8 encoder reports malformed input — meaning the string contains an unpaired or broken surrogate (the comment in the source confirms 'Must be a broken surrogate pair'). Valid Java Strings must not contain unpaired surrogates, but they can be constructed that way from bad binary data or \uD83x literals.
Source
Thrown at api/src/main/java/io/grpc/Uri.java:1113
throw new VerifyException(e); // Should not happen in REPLACE mode.
}
}
@Nullable
private static String percentEncode(String s, BitSet allowedCodePoints) {
if (s == null) {
return null;
}
CharsetEncoder encoder =
StandardCharsets.UTF_8
.newEncoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
ByteBuffer utf8Bytes;
try {
utf8Bytes = encoder.encode(CharBuffer.wrap(s));
} catch (MalformedInputException e) {
throw new IllegalArgumentException("Malformed input", e); // Must be a broken surrogate pair.
} catch (CharacterCodingException e) {
throw new VerifyException(e); // Should not happen when encoding to UTF-8.
}
StringBuilder sb = new StringBuilder();
while (utf8Bytes.hasRemaining()) {
int b = 0xff & utf8Bytes.get();
if (allowedCodePoints.get(b)) {
sb.append((char) b);
} else {
sb.append('%');
sb.append(hexDigitsByVal[(b & 0xF0) >> 4]);
sb.append(hexDigitsByVal[b & 0x0F]);
}
}
return sb.toString();
}
View on GitHub (pinned to 64daddc1f3)
Solutions
- Fix the source so the string contains only well-formed UTF-16 (paired surrogates)
- Sanitize the string with CharsetEncoder/CharsetDecoder using REPLACE before passing it: new String(s.getBytes(UTF_8)...) won't help; use s.codePoints() filtering or a REPLACE decoder round-trip
- Strip or replace unpaired surrogates explicitly via code-point iteration
- If reading from bytes, decode bytes to String with CodingErrorAction.REPLACE
Example fix
// before
builder.setPath("/u/" + rawStringFromBytes); // may contain unpaired surrogate
// after
String clean = new String(rawStringFromBytes.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16); // or filter:
String clean2 = rawStringFromBytes.codePoints().filter(cp -> Character.isValidCodePoint(cp) && !Character.isSurrogate((char) cp)).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(); Defensive patterns
Strategy: validation
Validate before calling
static String sanitizeSurrogates(String s) {
StringBuilder sb = new StringBuilder(s.length());
s.codePoints().forEach(cp -> {
if (Character.isValidCodePoint(cp) && !Character.isHighSurrogate((char) cp) && !Character.isLowSurrogate((char) cp)) {
sb.appendCodePoint(cp);
} else if (!Character.isBmpCodePoint(cp)) {
sb.appendCodePoint(cp);
}
});
return sb.toString();
} Try / catch
try { Uri.Builder.create(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("String contains broken surrogate pair, cannot UTF-8 encode: " + value, e); } Prevention
- Never build strings containing lone \uD83x-style surrogate escapes
- Decode binary data with CodingErrorAction.REPLACE before use
- Sanitize strings sourced from external systems
- Round-trip check: s.equals(new String(s.getBytes(UTF_8), UTF_8)) catches bad input early
When it happens
Trigger: Calling Uri.Builder.create/setPath-like entry that re-encodes the string when the input String contains an unpaired surrogate such as '\uD800' not followed by a low surrogate.
Common situations: Decoding binary data with the wrong charset and then building a URI from it; string literals with single surrogate escapes; interop with systems that produce ill-formed UTF-16.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid character in ${what} at index ${i}
- Invalid host or port:
- Has authority -- Non-empty path must start with '/'
- No authority -- Path cannot start with '//'
- Missing required scheme.
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/f50e0b403f18eec8.
Report an issue: GitHub.