apache/seatunnel · error · IOException

FTP LIST failed for path=${mask(absolute)}, replyCode=${clie

Error message

FTP LIST failed for path=${mask(absolute)}, replyCode=${client.getReplyCode()}

What it means

Thrown by SeaTunnelFTPFileSystem.list() when initiateListParsing() completes but the FTP reply code is not a positive completion (2xx), i.e. the LIST command itself failed even though the CWD succeeded. The path is masked in the message and the raw reply code is included for diagnosis.

Source

Thrown at seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java:538

        @Override
        public FileStatus getFileStatus(Path path) throws IOException {
            return SeaTunnelFTPFileSystem.this.getFileStatus(client, path);
        }

        @Override
        public void list(Path directory, FileStatusConsumer consumer) throws IOException {
            Path workDir = new Path(client.printWorkingDirectory());
            Path absolute = makeAbsolute(workDir, directory);
            String previousDirectory = client.printWorkingDirectory();
            if (!client.changeWorkingDirectory(absolute.toUri().getPath())) {
                throw new FileNotFoundException("FTP directory does not exist: " + mask(absolute));
            }
            Throwable failure = null;
            try {
                FTPListParseEngine engine = client.initiateListParsing();
                if (!FTPReply.isPositiveCompletion(client.getReplyCode())) {
                    throw new IOException(
                            "FTP LIST failed for path="
                                    + mask(absolute)
                                    + ", replyCode="
                                    + client.getReplyCode());
                }
                while (engine.hasNext()) {
                    FTPFile[] files = engine.getNext(PAGE_SIZE);
                    int skipped = emitFileStatuses(files, absolute, consumer);
                    if (skipped > 0) {
                        LOG.warn(
                                "Skipped "
                                        + skipped
                                        + " unparseable FTP listing entries under "
                                        + mask(absolute));
                    }
                }
            } catch (IOException | RuntimeException | Error e) {
                failure = e;

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Check the reply code: 421 → connection limit, 425/426 → data channel/network, 5xx → command rejected
  2. Switch to passive mode and open the required passive port range on the firewall
  3. Reduce job parallelism to stay under the FTP server's max connection limit
  4. Retry the listing on transient reply codes (4xx)
  5. Test LIST behavior manually with the same credentials via an FTP CLI client

Example fix

// before
// no retry, single list attempt
FileStatus[] files = fs.listStatus(dir);
// after
// retry transient FTP failures (4xx) with backoff before failing the job
for (int attempt = 0; attempt < 3; attempt++) {
    try { return fs.listStatus(dir); } catch (IOException e) { sleep(backoff(attempt)); }
}
Defensive patterns

Strategy: retry

Validate before calling

// verify LIST works before the job: open a test connection and list the base dir
// also confirm passive mode + open data ports in the firewall

Try / catch

for (int attempt = 1; attempt <= 3; attempt++) {
    try { return fs.listStatus(dir); }
    catch (IOException e) {
        if (e.getMessage().contains("FTP LIST failed") && attempt < 3) { backoffAndRetry(); }
        else throw e;
    }
}

Prevention

When it happens

Trigger: The FTP data channel for LIST could not be established or the server rejected LIST — passive-mode blocked by firewall/NAT, connection limit reached (421), transfer aborted (426), or server not supporting the default list parsing on that path.

Common situations: Corporate firewalls blocking the passive data port range; FTP server under max-connection limits due to high job parallelism; transient network drops during directory enumeration; misconfigured active/passive mode in the connector.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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