{"record":{"id":"0e7682cf5932e538","repo":"dotnet/aspnetcore","slug":"message-is-incomplete-0e7682","errorCode":null,"errorMessage":"Message is incomplete.","messagePattern":"Message is incomplete\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java","lineNumber":60,"sourceCode":"\n    @Override\n    public List<HubMessage> parseMessages(ByteBuffer payload, InvocationBinder binder) {\n        String payloadStr;\n        // If the payload is readOnly, we have to copy the bytes from its array to make the payload string\n        if (payload.isReadOnly()) {\n            byte[] payloadBytes = new byte[payload.remaining()];\n            payload.get(payloadBytes, 0, payloadBytes.length);\n            payloadStr = new String(payloadBytes, StandardCharsets.UTF_8);\n        // Otherwise we can allocate directly from its array\n        } else {\n            // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes\n            payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8);\n        }\n        if (payloadStr.length() == 0) {\n            return null;\n        }\n        if (!(payloadStr.substring(payloadStr.length() - 1).equals(RECORD_SEPARATOR))) {\n            throw new RuntimeException(\"Message is incomplete.\");\n        }\n\n        String[] messages = payloadStr.split(RECORD_SEPARATOR);\n        List<HubMessage> hubMessages = new ArrayList<>();\n        try {\n            for (String str : messages) {\n                HubMessageType messageType = null;\n                String invocationId = null;\n                String target = null;\n                String error = null;\n                ArrayList<Object> arguments = null;\n                JsonArray argumentsToken = null;\n                Object result = null;\n                Exception argumentBindingException = null;\n                JsonElement resultToken = null;\n                JsonReader reader = new JsonReader(new StringReader(str));\n                reader.beginObject();\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java#L42-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use the built-in WebSocketTransport/LongPollingTransport which buffer complete frames before handing them to the protocol.","If parsing manually in tests, always append RECORD_SEPARATOR: String payload = json + Character.toString((char)0x1e).","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.","Verify the server is an ASP.NET Core SignalR endpoint at the negotiated version."],"exampleFix":"// before (test)\nList<HubMessage> msgs = protocol.parseMessages(ByteBuffer.wrap(\"{\\\"type\\\":6}\".getBytes()), null); // throws\n\n// after\nString frame = \"{\\\"type\\\":6}\" + (char)0x1e;\nList<HubMessage> msgs = protocol.parseMessages(ByteBuffer.wrap(frame.getBytes(StandardCharsets.UTF_8)), null);","handlingStrategy":"validation","validationCode":"boolean isCompleteFrame(ByteBuffer buf) {\n  if (buf.remaining() == 0) return true; // empty is handled separately\n  byte last = buf.get(buf.limit() - 1);\n  return last == (byte) 0x1e;\n}\n\nif (!isCompleteFrame(payload)) {\n  // buffer more; do not call parseMessages","typeGuard":"boolean endsWithRecordSeparator(String s) {\n  return !s.isEmpty() && s.charAt(s.length() - 1) == 0x1e;\n}","tryCatchPattern":"try {\n  protocol.parseMessages(payload, null);\n} catch (RuntimeException e) {\n  if (\"Message is incomplete.\".equals(e.getMessage())) {\n    // re-buffer the partial frame and wait for more bytes\n  } else throw e;\n}","preventionTips":["Use built-in transports; they buffer whole frames before invoking the protocol.","In tests, append (char)0x1e to every payload.","Verify the server is a real SignalR endpoint and no proxy strips control bytes."],"tags":["signalr","java","protocol","framing","transport"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}