dotnet/aspnetcore · error · RuntimeException

Message is incomplete.

Error message

Message is incomplete.

What it means

Thrown by GsonHubProtocol.parseMessages when the UTF-8 payload string does not end with the RECORD_SEPARATOR character (U+001E). Mirror of the TS TextMessageFormat guard (error 160): the Java JSON protocol splits frames on 0x1E and rejects an unterminated frame rather than silently dropping it. Empty payloads return null earlier, so this throw specifically means non-empty but unframed input.

Solutions

  1. Use the built-in WebSocketTransport/LongPollingTransport which buffer complete frames before handing them to the protocol.
  2. If parsing manually in tests, always append RECORD_SEPARATOR: String payload = json + Character.toString((char)0x1e).
  3. Inspect the server response: a missing 0x1E on the wire usually means you connected to a non-SignalR endpoint or a proxy stripped the byte.
  4. Verify the server is an ASP.NET Core SignalR endpoint at the negotiated version.

Example fix

// before (test)
List<HubMessage> msgs = protocol.parseMessages(ByteBuffer.wrap("{\"type\":6}".getBytes()), null); // throws

// after
String frame = "{\"type\":6}" + (char)0x1e;
List<HubMessage> msgs = protocol.parseMessages(ByteBuffer.wrap(frame.getBytes(StandardCharsets.UTF_8)), null);
Defensive patterns

Strategy: validation

Validate before calling

boolean isCompleteFrame(ByteBuffer buf) {
  if (buf.remaining() == 0) return true; // empty is handled separately
  byte last = buf.get(buf.limit() - 1);
  return last == (byte) 0x1e;
}

if (!isCompleteFrame(payload)) {
  // buffer more; do not call parseMessages

Type guard

boolean endsWithRecordSeparator(String s) {
  return !s.isEmpty() && s.charAt(s.length() - 1) == 0x1e;
}

Try / catch

try {
  protocol.parseMessages(payload, null);
} catch (RuntimeException e) {
  if ("Message is incomplete.".equals(e.getMessage())) {
    // re-buffer the partial frame and wait for more bytes
  } else throw e;
}

Prevention

When it happens

Trigger: A ByteBuffer passed to parseMessages whose remaining bytes do not terminate with 0x1E; a custom transport that delivers a sliced frame; a server or proxy that strips the record separator; malformed test payloads.

Common situations: Replacing the default transport with one that does not buffer until a full frame; an intermediary (load balancer/proxy) truncating control characters; version mismatch where a non-SignalR endpoint answered the connection; tests feeding raw JSON without the separator.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/0e7682cf5932e538. Report an issue: GitHub.

Appendix: source

Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java:60

    @Override
    public List<HubMessage> parseMessages(ByteBuffer payload, InvocationBinder binder) {
        String payloadStr;
        // If the payload is readOnly, we have to copy the bytes from its array to make the payload string
        if (payload.isReadOnly()) {
            byte[] payloadBytes = new byte[payload.remaining()];
            payload.get(payloadBytes, 0, payloadBytes.length);
            payloadStr = new String(payloadBytes, StandardCharsets.UTF_8);
        // Otherwise we can allocate directly from its array
        } else {
            // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes
            payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8);
        }
        if (payloadStr.length() == 0) {
            return null;
        }
        if (!(payloadStr.substring(payloadStr.length() - 1).equals(RECORD_SEPARATOR))) {
            throw new RuntimeException("Message is incomplete.");
        }

        String[] messages = payloadStr.split(RECORD_SEPARATOR);
        List<HubMessage> hubMessages = new ArrayList<>();
        try {
            for (String str : messages) {
                HubMessageType messageType = null;
                String invocationId = null;
                String target = null;
                String error = null;
                ArrayList<Object> arguments = null;
                JsonArray argumentsToken = null;
                Object result = null;
                Exception argumentBindingException = null;
                JsonElement resultToken = null;
                JsonReader reader = new JsonReader(new StringReader(str));
                reader.beginObject();

View on GitHub (pinned to 3600ca084e)