apache/dubbo · error · IOException

Stream closed

Error message

Stream closed

What it means

Thrown by UnsafeStringReader.ensureOpen() (a private guard invoked by read, read(char[],int,int), skip, ready, reset, and mark) when mString == null, i.e. after close() has been called. close() sets mString = null as its sole action, so every subsequent read-family operation trips this IOException. It is the standard 'use after close' signal matching java.io.StringReader.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/io/UnsafeStringReader.java:114

        ensureOpen();
        mMark = mPosition;
    }

    @Override
    public void reset() throws IOException {
        ensureOpen();
        mPosition = mMark;
    }

    @Override
    public void close() throws IOException {
        mString = null;
    }

    private void ensureOpen() throws IOException {
        if (mString == null) {
            throw new IOException("Stream closed");
        }
    }
}

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Do not use the reader after close() — treat close() as terminal. If using try-with-resources, move all reads inside the try block.
  2. Ensure single ownership: only the component that opens the reader closes it.
  3. If a reader field may be reused, null it out after close and create a fresh UnsafeStringReader per use.
  4. Catch IOException at the call site and treat 'Stream closed' as end-of-lifecycle rather than retrying.

Example fix

// before
try (UnsafeStringReader r = new UnsafeStringReader(s)) {
    parse(r);
}
int c = r.read(); // closed -> IOException
// after
try (UnsafeStringReader r = new UnsafeStringReader(s)) {
    parse(r);
    int c = r.read(); // read inside the scope
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check whether the reader is still open by attempting ready() inside try-with-resources scope.
// There is no public isOpen(); the only signal is the IOException itself.

Try / catch

try {
    int c = reader.read();
} catch (IOException e) {
    if ("Stream closed".equals(e.getMessage())) {
        // reader already closed; treat as end-of-lifecycle, do not reuse
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: Any sequence: reader.close(); then reader.read(), reader.skip(n), reader.ready(), reader.reset(), or reader.mark(n) on the same UnsafeStringReader. Also reached if close() is called twice and the second close somehow routes through ensureOpen (it does not — close() itself never calls ensureOpen, so only post-close read operations trigger it).

Common situations: Try-with-resources block where code uses the reader after the try exits; sharing a reader across components where one calls close() and another reads; reusing a cached reader field that was closed by a previous request; finalizer/cleanup ordering bugs.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/89d03ba1629cec7f. Report an issue: GitHub.