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();
                }

                @Override

View on GitHub (pinned to 626827cddb)

Solutions

  1. 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().
  2. If the exact size is hard to know, switch to setChunkedStreamingMode().
  3. 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

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


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/31f897bf5976284b. Report an issue: GitHub.