{"record":{"id":"f96461c34a93ce21","repo":"apache/seatunnel","slug":"ftp-list-failed-for-path-mask-absolute-replyc","errorCode":null,"errorMessage":"FTP LIST failed for path=${mask(absolute)}, replyCode=${client.getReplyCode()}","messagePattern":"FTP LIST failed for path=(.+?), replyCode=(.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java","lineNumber":538,"sourceCode":"\n        @Override\n        public FileStatus getFileStatus(Path path) throws IOException {\n            return SeaTunnelFTPFileSystem.this.getFileStatus(client, path);\n        }\n\n        @Override\n        public void list(Path directory, FileStatusConsumer consumer) throws IOException {\n            Path workDir = new Path(client.printWorkingDirectory());\n            Path absolute = makeAbsolute(workDir, directory);\n            String previousDirectory = client.printWorkingDirectory();\n            if (!client.changeWorkingDirectory(absolute.toUri().getPath())) {\n                throw new FileNotFoundException(\"FTP directory does not exist: \" + mask(absolute));\n            }\n            Throwable failure = null;\n            try {\n                FTPListParseEngine engine = client.initiateListParsing();\n                if (!FTPReply.isPositiveCompletion(client.getReplyCode())) {\n                    throw new IOException(\n                            \"FTP LIST failed for path=\"\n                                    + mask(absolute)\n                                    + \", replyCode=\"\n                                    + client.getReplyCode());\n                }\n                while (engine.hasNext()) {\n                    FTPFile[] files = engine.getNext(PAGE_SIZE);\n                    int skipped = emitFileStatuses(files, absolute, consumer);\n                    if (skipped > 0) {\n                        LOG.warn(\n                                \"Skipped \"\n                                        + skipped\n                                        + \" unparseable FTP listing entries under \"\n                                        + mask(absolute));\n                    }\n                }\n            } catch (IOException | RuntimeException | Error e) {\n                failure = e;","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java#L520-L556","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the reply code: 421 → connection limit, 425/426 → data channel/network, 5xx → command rejected","Switch to passive mode and open the required passive port range on the firewall","Reduce job parallelism to stay under the FTP server's max connection limit","Retry the listing on transient reply codes (4xx)","Test LIST behavior manually with the same credentials via an FTP CLI client"],"exampleFix":"// before\n// no retry, single list attempt\nFileStatus[] files = fs.listStatus(dir);\n// after\n// retry transient FTP failures (4xx) with backoff before failing the job\nfor (int attempt = 0; attempt < 3; attempt++) {\n    try { return fs.listStatus(dir); } catch (IOException e) { sleep(backoff(attempt)); }\n}","handlingStrategy":"retry","validationCode":"// verify LIST works before the job: open a test connection and list the base dir\n// also confirm passive mode + open data ports in the firewall","typeGuard":null,"tryCatchPattern":"for (int attempt = 1; attempt <= 3; attempt++) {\n    try { return fs.listStatus(dir); }\n    catch (IOException e) {\n        if (e.getMessage().contains(\"FTP LIST failed\") && attempt < 3) { backoffAndRetry(); }\n        else throw e;\n    }\n}","preventionTips":["Enable passive mode and allow the passive port range through firewalls/NAT","Keep job parallelism under the FTP server's max-connection limit","Retry on transient 4xx reply codes","Validate LIST behavior manually with the same user before production runs"],"tags":["ftp","list","network","passive-mode"],"backgroundTag":"http-request-failed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}