{"record":{"id":"f50e0b403f18eec8","repo":"grpc/grpc-java","slug":"malformed-input","errorCode":null,"errorMessage":"Malformed input","messagePattern":"Malformed input","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/io/grpc/Uri.java","lineNumber":1113,"sourceCode":"      throw new VerifyException(e); // Should not happen in REPLACE mode.\n    }\n  }\n\n  @Nullable\n  private static String percentEncode(String s, BitSet allowedCodePoints) {\n    if (s == null) {\n      return null;\n    }\n    CharsetEncoder encoder =\n        StandardCharsets.UTF_8\n            .newEncoder()\n            .onMalformedInput(CodingErrorAction.REPORT)\n            .onUnmappableCharacter(CodingErrorAction.REPORT);\n    ByteBuffer utf8Bytes;\n    try {\n      utf8Bytes = encoder.encode(CharBuffer.wrap(s));\n    } catch (MalformedInputException e) {\n      throw new IllegalArgumentException(\"Malformed input\", e); // Must be a broken surrogate pair.\n    } catch (CharacterCodingException e) {\n      throw new VerifyException(e); // Should not happen when encoding to UTF-8.\n    }\n\n    StringBuilder sb = new StringBuilder();\n    while (utf8Bytes.hasRemaining()) {\n      int b = 0xff & utf8Bytes.get();\n      if (allowedCodePoints.get(b)) {\n        sb.append((char) b);\n      } else {\n        sb.append('%');\n        sb.append(hexDigitsByVal[(b & 0xF0) >> 4]);\n        sb.append(hexDigitsByVal[b & 0x0F]);\n      }\n    }\n    return sb.toString();\n  }\n","sourceCodeStart":1095,"sourceCodeEnd":1131,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/api/src/main/java/io/grpc/Uri.java#L1095-L1131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbuilder.setPath(\"/u/\" + rawStringFromBytes); // may contain unpaired surrogate\n// after\nString clean = new String(rawStringFromBytes.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16); // or filter:\nString clean2 = rawStringFromBytes.codePoints().filter(cp -> Character.isValidCodePoint(cp) && !Character.isSurrogate((char) cp)).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();","handlingStrategy":"validation","validationCode":"static String sanitizeSurrogates(String s) {\n  StringBuilder sb = new StringBuilder(s.length());\n  s.codePoints().forEach(cp -> {\n    if (Character.isValidCodePoint(cp) && !Character.isHighSurrogate((char) cp) && !Character.isLowSurrogate((char) cp)) {\n      sb.appendCodePoint(cp);\n    } else if (!Character.isBmpCodePoint(cp)) {\n      sb.appendCodePoint(cp);\n    }\n  });\n  return sb.toString();\n}","typeGuard":null,"tryCatchPattern":"try { Uri.Builder.create(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(\"String contains broken surrogate pair, cannot UTF-8 encode: \" + value, e); }","preventionTips":["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"],"tags":["uri","utf-8","encoding","surrogate"],"backgroundTag":"invalid-argument-format","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}