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
- Ensure no read() calls happen after close(); restructure so the stream is read fully inside try-with-resources.
- If the stream was closed due to cancellation/error, reopen a new SFTPInputStream instead of reusing the old object.
- Check for double-close in wrapper code (e.g. closing both the wrapped stream and this stream then reading).
- 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
- Always read streams inside try-with-resources
- Never reuse a stream object after close(); reopen instead
- Keep close() and read() on the same thread or synchronize access
- Initialize stream references inside the scope that consumes them
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
- SftpException wrapped (pwd failed while deleting)
- SftpException wrapped (pwd failed while listing status)
- SftpException wrapped (ls failed while listing status)
- Path %s is a directory.
- Generic exception wrapped (open failed)
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/233a223c897a18d6.
Report an issue: GitHub.