pentaho/pentaho-kettle · error · SftpException
Failed to download file:
Error message
Failed to download file:
What it means
MinaSftpSession.download() wraps IOException from client.read(remote) and stream transfer into an SftpException. The remote file could not be opened/read or the local output stream failed mid-transfer. Any interruption of the data channel aborts the download with this message.
Solutions
- Retry the download with backoff; SFTP transfers are safe to retry into a fresh stream
- Verify the local OutputStream target is writable and has enough disk space
- Check the remote file exists and is readable before downloading
- For large transfers, keep-alive or increase SSH session timeouts to survive long reads
Example fix
// before
session.download(remote, new FileOutputStream(localFile));
// after
File outFile = new File(localFile);
try (OutputStream out = new FileOutputStream(outFile)) {
session.download(remote, out);
} catch (IOException io) {
throw new IOException("Local write failed for " + localFile, io);
} Defensive patterns
Strategy: retry
Validate before calling
try { long expected = session.size(remote); if (expected <= 0) throw new IllegalStateException("empty/missing remote file"); }
catch (SftpException e) { throw new IllegalStateException("remote not downloadable: " + remote, e); }
// ensure local target writable
if (!localFile.getParentFile().canWrite()) throw new IllegalStateException("local dir not writable"); Type guard
boolean downloadable(MinaSftpSession s, String r) {
try { return s.size(r) >= 0; } catch (SftpException e) { return false; }
} Try / catch
for (int i = 0; i < 3; i++) {
try (OutputStream out = new FileOutputStream(tmpFile)) {
session.download(remote, out);
Files.move(tmpFile.toPath(), localPath, StandardCopyOption.REPLACE_EXISTING);
break;
} catch (SftpException | IOException e) {
if (i == 2) throw new IOException("download failed after retries", e);
sleep(backoff(i));
}
} Prevention
- Download to a temp file and rename atomically so retries never leave corrupt partials
- Increase session/server timeouts and enable keep-alive for large files
- Verify disk space and write permission on the local target before starting
- Retry transient IO failures with backoff; check e.getCause() to classify permanent vs transient
When it happens
Trigger: Calling download(remote, target) when the remote file is missing/unreadable, the network drops mid-transfer, the SFTP session times out, or the local OutputStream (e.g. FileOutputStream) throws while writing.
Common situations: Large-file downloads interrupted by NAT/firewall idle timeouts, disk-full or permission errors on the local output file, remote file deleted between listing and download, or concurrent modification of the remote file.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Failed to check if path is directory:
- Failed to get file size:
- Failed to open SFTP session
- Failed to upload file:
- SFTPPUT.Error.Connection
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/f0c88c4efe4c5667.
Report an issue: GitHub.
Appendix: source
Thrown at engine/src/main/java/org/pentaho/di/core/ssh/mina/MinaSftpSession.java:90
}
@Override
public long size( String path ) throws SftpException {
try {
return client.stat( path ).getSize();
} catch ( IOException e ) {
throw new SftpException( "Failed to get file size: " + path, e );
}
}
@Override
public void download( String remote, OutputStream target ) throws SftpException {
try {
try ( InputStream in = client.read( remote ) ) {
in.transferTo( target );
}
} catch ( IOException e ) {
throw new SftpException( "Failed to download file: " + remote, e );
}
}
@Override
public void upload( InputStream source, String remote, boolean overwrite ) throws SftpException {
try {
try ( SftpClient.CloseableHandle h = client.open( remote, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create,
SftpClient.OpenMode.Truncate ) ) {
byte[] buf = new byte[ 8192 ];
int r;
long off = 0;
while ( ( r = source.read( buf ) ) >= 0 ) {
if ( r == 0 ) {
continue;
}
client.write( h, off, buf, 0, r );
off += r;
}View on GitHub (pinned to f3058517a1)