apache/seatunnel · error · IOException
Directory: ${file} is not empty.
Error message
Directory: ${file} is not empty. What it means
Thrown by SeaTunnelFTPFileSystem.delete() when the target path is a directory that still contains entries and recursive=false. The FTP filesystem refuses to delete a non-empty directory without the recursive flag to prevent accidental data loss.
Source
Thrown at seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java:458
* another method. Otherwise every API invocation incurs the overhead of opening/closing a TCP
* connection.
*/
private boolean delete(FTPClient client, Path file, boolean recursive) throws IOException {
Path workDir = new Path(client.printWorkingDirectory());
Path absolute = makeAbsolute(workDir, file);
String pathName = absolute.toUri().getPath();
try {
FileStatus fileStat = getFileStatus(client, absolute);
if (fileStat.isFile()) {
return client.deleteFile(pathName);
}
} catch (FileNotFoundException e) {
// the file is not there
return false;
}
FileStatus[] dirEntries = listStatus(client, absolute);
if (dirEntries != null && dirEntries.length > 0 && !recursive) {
throw new IOException("Directory: " + file + " is not empty.");
}
if (dirEntries != null) {
for (int i = 0; i < dirEntries.length; i++) {
delete(client, new Path(absolute, dirEntries[i].getPath()), recursive);
}
}
return client.removeDirectory(pathName);
}
private FsAction getFsAction(int accessGroup, FTPFile ftpFile) {
FsAction action = FsAction.NONE;
if (ftpFile.hasPermission(accessGroup, FTPFile.READ_PERMISSION)) {
action.or(FsAction.READ);
}
if (ftpFile.hasPermission(accessGroup, FTPFile.WRITE_PERMISSION)) {
action.or(FsAction.WRITE);
}
if (ftpFile.hasPermission(accessGroup, FTPFile.EXECUTE_PERMISSION)) {View on GitHub (pinned to cf67b549a7)
Solutions
- Call fs.delete(path, true) to delete the directory and all its contents
- Delete the child files first, then the now-empty directory
- Manually clean the directory on the FTP server if only partial cleanup is intended
- Guard the delete call with a check of listStatus() length when you intentionally keep contents
Example fix
// before fs.delete(outputDir, false); // after fs.delete(outputDir, true); // recursive delete of non-empty directory
Defensive patterns
Strategy: validation
Validate before calling
FileStatus[] entries = fs.listStatus(dir);
if (entries.length > 0) {
fs.delete(dir, true); // recursive required for non-empty dirs
} else {
fs.delete(dir, false);
} Try / catch
try {
fs.delete(dir, false);
} catch (IOException e) {
if (e.getMessage().contains("is not empty")) {
fs.delete(dir, true); // escalate to recursive when contents are disposable
} else { throw e; }
} Prevention
- Default to recursive=true for directory cleanup unless contents must be preserved
- Inspect listStatus() before deleting to decide the recursive flag
- Clean temp/staging directories as part of job teardown
When it happens
Trigger: Calling fs.delete(dirPath, false) on a directory that lists at least one child entry via listStatus(). Also reached indirectly when delete() recurses or is used internally by create/success cleanup paths.
Common situations: Cleaning up an output directory before a job run with overwrite semantics but without recursion; temp/staging directories that were not emptied; users passing recursive=false by default from generic Hadoop FileSystem wrapper code.
Related errors
- Unable to open file: ${file}, Aborting
- Unable to create file: ${file}, Aborting
- Can't make directory for path %s since it is a file.
- Failed to create directory %s in %s, FTP reply code: %d, rep
- File check failed
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/6c54c09151238ef7.
Report an issue: GitHub.