didi/DoKit · error · ProtocolException
expected " + expectedContentLength + " bytes but received "
Error message
expected " + expectedContentLength + " bytes but received " + bytesReceived + byteCount
What it means
In fixed-length mode (expectedContentLength != -1, set via setFixedLengthStreamingMode), the body OutputStream counts written bytes and throws ProtocolException("expected N bytes but received M") as soon as writes would exceed the declared length. The declared content length and the actual bytes written must match exactly.
Source
Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/urlconnection/ObsoleteUrlFactory.java:836
this.timeout = sink.timeout();
this.expectedContentLength = expectedContentLength;
// An output stream that writes to sink. If expectedContentLength is not -1, then this expects
// exactly that many bytes to be written.
this.outputStream = new OutputStream() {
private long bytesReceived;
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte) b}, 0, 1);
}
@Override
public void write(byte[] source, int offset, int byteCount) throws IOException {
if (closed) throw new IOException("closed"); // Not IllegalStateException!
if (expectedContentLength != -1L && bytesReceived + byteCount > expectedContentLength) {
throw new ProtocolException("expected " + expectedContentLength
+ " bytes but received " + bytesReceived + byteCount);
}
bytesReceived += byteCount;
try {
sink.write(source, offset, byteCount);
} catch (InterruptedIOException e) {
throw new SocketTimeoutException(e.getMessage());
}
}
@Override
public void flush() throws IOException {
if (closed) return; // Weird, but consistent with historical behavior.
sink.flush();
}
@OverrideView on GitHub (pinned to 626827cddb)
Solutions
- Recompute the declared length to include every byte (boundaries, CRLFs, headers inside the body) — build the body into a ByteArrayOutputStream first and use its size().
- If the exact size is hard to know, switch to setChunkedStreamingMode().
- Write the body once from a single code path to avoid duplicate writes.
Example fix
// before conn.setFixedLengthStreamingMode(file.length()); os.write(multipartHeader); os.write(fileBytes); // exceeds file.length() // after byte[] body = buildMultipart(file); // header+file+boundary conn.setFixedLengthStreamingMode(body.length); os.write(body);
Defensive patterns
Strategy: validation
Validate before calling
byte[] body = buildBody(); // exact bytes you will write conn.setFixedLengthStreamingMode(body.length); // then write exactly body
Prevention
- Buffer the full body and use its size() as the declared length.
- If exact size is uncertain, use chunked mode instead of guessing.
When it happens
Trigger: Calling setFixedLengthStreamingMode(n) but writing more than n bytes — e.g. declaring file.length() then adding headers/boundaries on top, or writing the body twice.
Common situations: Multipart uploads where the declared length omits boundary bytes; declaring a compressed/inflated length different from the actual bytes; off-by-one or duplicated chunk writes.
Related errors
- expected " + expectedContentLength + " bytes but received "
- Cannot access request header fields after connection is set
- This protocol does not support input
- method does not support a request body: ${method}
- cannot write request body after response has been read
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/31f897bf5976284b.
Report an issue: GitHub.