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.

Solutions

  1. Fix the source so the string contains only well-formed UTF-16 (paired surrogates)
  2. 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
  3. Strip or replace unpaired surrogates explicitly via code-point iteration
  4. 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

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.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/f50e0b403f18eec8. Report an issue: GitHub.

Appendix: 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)