apache/seatunnel · error · FileNotFoundException
Path ${file} is a directory.
Error message
Path ${file} is a directory. What it means
SeaTunnelFTPFileSystem.open() first resolves the file status; if the target path is a directory it throws java.io.FileNotFoundException with 'Path <file> is a directory.' This guards the read path because directories cannot be opened as input streams.
Source
Thrown at seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java:310
}
if (filePath.startsWith(workDir.toUri().getPath())) {
return path;
}
// delete '/'
return new Path(workDir, filePath.substring(1));
}
return new Path(workDir, path);
}
@Override
public FSDataInputStream open(Path file, int bufferSize) throws IOException {
FTPClient client = connect();
Path workDir = new Path(client.printWorkingDirectory());
Path absolute = makeAbsolute(workDir, file);
FileStatus fileStat = getFileStatus(client, absolute);
if (fileStat.isDirectory()) {
disconnect(client);
throw new FileNotFoundException("Path " + file + " is a directory.");
}
client.allocate(bufferSize);
Path parent = absolute.getParent();
// Change to parent directory on the
// server. Only then can we read the
// file
// on the server by opening up an InputStream. As a side effect the working
// directory on the server is changed to the parent directory of the file.
// The FTP client connection is closed when close() is called on the
// FSDataInputStream.
client.changeWorkingDirectory(parent.toUri().getPath());
InputStream is = client.retrieveFileStream(file.getName());
FSDataInputStream fis = new FSDataInputStream(new FTPInputStream(is, client, statistics));
if (!FTPReply.isPositivePreliminary(client.getReplyCode())) {
// The ftpClient is an inconsistent state. Must close the stream
// which in turn will logout and disconnect from FTP server
fis.close();
throw new IOException("Unable to open file: " + file + ", Aborting");View on GitHub (pinned to cf67b549a7)
Solutions
- Point open()/the config at an actual file path, not a directory
- List the parent directory to confirm the intended file exists
- Remove the trailing slash or directory component from the path
Example fix
// before
fs.open(new Path("ftp://host/data/reports")) // a directory
// after
fs.open(new Path("ftp://host/data/reports/2024.csv")) Defensive patterns
Strategy: validation
Validate before calling
// Java
FileSystem fs = path.getFileSystem(conf);
FileStatus st = fs.getFileStatus(path);
if (st.isDirectory()) throw new IllegalArgumentException("Expected a file, got directory: " + path); Try / catch
try {
return fs.open(path);
} catch (FileNotFoundException e) {
if (e.getMessage() != null && e.getMessage().endsWith("is a directory.")) {
throw new IllegalArgumentException("Point the source at a file, not a directory: " + path, e);
}
throw e;
} Prevention
- Resolve globs/directories into concrete file lists before opening
- Strip trailing slashes from configured paths
- Re-verify the target still exists as a file right before open (race with renames)
When it happens
Trigger: Calling open(file) where getFileStatus() reports the path as a directory — e.g. the configured path in the source points at a directory rather than a file, or a filename match pattern resolved to a directory.
Common situations: SeaTunnel file source configured with a directory path where a concrete file was expected; path constructed with a trailing slash; a rename/move left the expected file missing and a directory of the same name present.
Related errors
- File already exists: ${file}
- Invalid host specified
- Invalid user/password specified
- Unable to open file: ${file}, Aborting
- create(): Mkdirs failed to create: ${parent}
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/bb2fa369fe666b73.
Report an issue: GitHub.