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

  1. Call fs.delete(path, true) to delete the directory and all its contents
  2. Delete the child files first, then the now-empty directory
  3. Manually clean the directory on the FTP server if only partial cleanup is intended
  4. 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

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


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