apache/seatunnel · error · IOException

Stream closed

Error message

Stream closed

What it means

SFTPInputStream.read() throws IOException("Stream closed") when the stream has already been closed via close(). The wrapper tracks a 'closed' flag and refuses any further reads on the wrapped SFTP input stream. This is a defensive guard against using a closed stream, which would otherwise fail deeper inside JSch with confusing errors.

Source

Thrown at seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/system/SFTPInputStream.java:83

    @Override
    public void seek(long position) throws IOException {
        throw new IOException(E_SEEK_NOT_SUPPORTED);
    }

    @Override
    public boolean seekToNewSource(long targetPos) throws IOException {
        throw new IOException(E_SEEK_NOT_SUPPORTED);
    }

    @Override
    public long getPos() throws IOException {
        return pos;
    }

    @Override
    public synchronized int read() throws IOException {
        if (closed) {
            throw new IOException(E_STREAM_CLOSED);
        }

        int byteRead = wrappedStream.read();
        if (byteRead >= 0) {
            pos++;
        }
        if (stats != null & byteRead >= 0) {
            stats.incrementBytesRead(1);
        }
        return byteRead;
    }

    public synchronized int read(byte[] buf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(E_STREAM_CLOSED);
        }

        int result = wrappedStream.read(buf, off, len);

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Ensure no read() calls happen after close(); restructure so the stream is read fully inside try-with-resources.
  2. If the stream was closed due to cancellation/error, reopen a new SFTPInputStream instead of reusing the old object.
  3. Check for double-close in wrapper code (e.g. closing both the wrapped stream and this stream then reading).
  4. Guard concurrent access: reads and closes should be sequenced by the same thread or external synchronization.

Example fix

// before
InputStream in = openSftpStream();
in.close();
int b = in.read(); // IOException: Stream closed
// after
try (InputStream in = openSftpStream()) {
    int b = in.read(); // read while open
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (in instanceof SFTPInputStream && ((SFTPInputStream) in).isClosed()) { reopenStream(); }

Type guard

boolean isReadable(InputStream in) { return in != null && !(in instanceof SFTPInputStream s) || !s.isClosed(); }

Try / catch

try { int b = in.read(); } catch (IOException e) { if ("Stream closed".equals(e.getMessage())) { in = reopenStream(); } else { throw e; } }

Prevention

When it happens

Trigger: Calling read() after close() has been invoked on the SFTPInputStream; a reader loop that keeps reading after the connector closed the stream; double-closing and then reading.

Common situations: Downstream code closing the stream in a try-with-resources block but accidentally reading afterwards; retry logic reusing a stale stream object after task cancellation; reader thread racing with a close from another thread (close is not synchronized with read guard state in caller code).

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/233a223c897a18d6. Report an issue: GitHub.