apache/seatunnel · error · java.net.ConnectException
Server response ${reply}
Error message
Server response ${reply} What it means
The FTP file system's connect() opened a TCP socket to the configured host:port, but the server's first reply code was not a positive completion (not 1xx/2xx/3xx per FTPReply.isPositiveCompletion). The connector wraps the raw reply code in a ConnectException via NetUtils.wrapException, so the message looks like 'Server response 421' (or 530, 500, etc.). The socket connected, but the FTP service refused to proceed — e.g. server overloaded, TLS required, or the port is not actually an FTP endpoint.
Source
Thrown at seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java:165
// Check if remote verification is enabled
boolean remoteVerificationEnabled =
conf.getBoolean(
FS_FTP_REMOTE_VERIFICATION_ENABLED,
FtpFileBaseOptions.FTP_REMOTE_VERIFICATION_ENABLED.defaultValue());
client.setRemoteVerificationEnabled(remoteVerificationEnabled);
// Retrieve host, port, user, and password from configuration
String host = conf.get(FS_FTP_HOST);
int port = conf.getInt(FS_FTP_HOST_PORT, FTP.DEFAULT_PORT);
String user = conf.get(FS_FTP_USER_PREFIX + host);
String password = conf.get(FS_FTP_PASSWORD_PREFIX + host);
// Connect to the FTP server
client.connect(host, port);
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
throw NetUtils.wrapException(
host,
port,
NetUtils.UNKNOWN_HOST,
0,
new ConnectException("Server response " + reply));
}
// Log in to the FTP server
if (!client.login(user, password)) {
throw new IOException(
String.format(
"Login failed on server - %s, port - %d as user '%s', reply code: %d",
host, port, user, client.getReplyCode()));
}
// Set the file type to binary and buffer size
client.setFileType(FTP.BINARY_FILE_TYPE);
client.setBufferSize(DEFAULT_BUFFER_SIZE);View on GitHub (pinned to cf67b549a7)
Solutions
- Read the reply code in the message: 421 = server busy/max connections (retry later or raise server connection limit); 530/needs-TLS = switch to an FTPS-capable setup or enable TLS; 500/502 = you are not talking to an FTP server (check host/port).
- Verify the host and port config (fs.ftp.host, fs.ftp.host-port) point at a plain FTP (or explicit-FTPS) endpoint — test with 'ftp host port' or a client like FileZilla and compare the banner.
- If the server requires FTPS, this SeaTunnelFTPFileSystem uses commons-net FTPClient without TLS — use an FTP server that allows plain FTP, or front it with a plain-FTP proxy/stunnel.
- Check server-side limits and health (max clients, IP allowlist, fail2ban) and retry with backoff on transient 421 codes.
- Confirm firewall/security-group rules permit the data-channel ports too, since some servers reject control sessions when the data channel cannot be established.
Example fix
// before: wrong port (SFTP server) -> Server response 500 String host = "sftp.example.com"; conf.set(FS_FTP_HOST, host); conf.setInt(FS_FTP_HOST_PORT, 22); // SFTP, not FTP // after: point at the real FTP service port conf.set(FS_FTP_HOST, "ftp.example.com"); conf.setInt(FS_FTP_HOST_PORT, 21); // standard FTP control port
Defensive patterns
Strategy: retry
Validate before calling
// Validate reachability and banner before submitting the job
try (Socket s = new Socket()) {
s.connect(new InetSocketAddress(host, port), 5000);
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String banner = in.readLine(); // e.g. "220 ftp.example.com FTP server ready"
if (banner == null || !banner.startsWith("220")) {
throw new IllegalStateException("Endpoint does not look like a healthy FTP server: " + banner);
}
} catch (java.net.UnknownHostException | java.net.SocketTimeoutException e) {
throw new IllegalStateException("Cannot reach FTP host/port: " + host + ":" + port, e);
} Try / catch
int attempts = 0;
while (true) {
try {
ftpFileSystem.list(...); // triggers connect()
break;
} catch (IOException e) {
Matcher m = Pattern.compile("Server response (\\d+)").matcher(e.getMessage());
if (m.find()) {
int reply = Integer.parseInt(m.group(1));
if (reply == 421 && ++attempts <= 3) { // transient: server busy
Thread.sleep(2000L * attempts);
continue;
}
}
throw e; // 530/500 etc. are configuration errors, do not retry
}
} Prevention
- Confirm the endpoint serves plain FTP (banner test with nc/ftp) — not SFTP or FTPS-only — before wiring the connector.
- Check server-side max-client limits and monitor 421 codes under load; raise limits or add retry with backoff.
- Verify host/port config against the actual FTP daemon (default control port 21).
- Ensure firewalls allow both the control connection and passive/active data-channel ports.
- If the server mandates TLS (530 before AUTH TLS), use an FTP server allowing plain FTP or a plain-FTP proxy, since this client does not do FTPS.
When it happens
Trigger: SeaTunnelFTPFileSystem.connect() — triggered on client(), openFileStatusListingSession(), getHomeDirectory() — when client.connect(host, port) succeeds at TCP level but client.getReplyCode() returns a non-positive-completion code: 421 (service not available / too many connections), 530 (not logged in / TLS required before commands), 500/502 (not an FTP server on that port), 120 (service ready in N minutes).
Common situations: Connecting to an FTPS-only server that requires AUTH TLS before serving commands; hitting a max-connections limit (421); pointing fs.ftp.host at the wrong port or at an SFTP/HTTP service (500); firewall/proxy allowing TCP connect but the FTP daemon rejecting; server under load during peak hours.
Related errors
- Client not connected
- CREATE_ACTIVEMQ_CLIENT_FAILED
- CONNECTION_FAILED
- CONNECTION_FAILED
- Timed out after <actualSeconds> seconds while waiting to con
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/9aaf1d765d59911e.
Report an issue: GitHub.