Java FTP, FTPS, and SFTP: Production File Transfer Patterns

File transfers look simple until they run inside a production Java system. A failed download can leave a partial file, a slow remote server can block a worker thread, and the wrong protocol choice can turn credentials into an operational risk.

This guide compares FTP, FTPS, and SFTP from a backend engineering point of view, then shows Java examples for listing, downloading, uploading, renaming, deleting, and resuming files. The goal is not only to connect. The goal is to make file transfer behavior predictable when networks fail.

For related production patterns, see how to connect SSH with Java, handling large PDF generation in Java, and the Spring Boot Production Lessons hub.

How FTP file transfer behaves in production

FTP (File Transfer Protocol) transfers files between computers over a network. It uses a client-server model, where the client requests files, and the server sends or receives them via commands.

FTP is not secure, but if explicitly used inside the local network or if the client’s internet speed is poor and the client wants to download large files, the developer can use this protocol because FTP supports file resuming, which prevents the download of entire files when the connection is lost.

Sometimes, the developer is confused between FTPS and SFTP.

FTPS vs SFTP: choose by failure mode, not name

What is different between FTPS and SFTP is the way they connect to the server.

FTPS and SFTP are secure and suitable for transferring files across the Internet.

Let’s start with the FTP list: download, upload, rename, and delete the file.

Java FTP and FTPS client example

1. Add Maven dependency.

<!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
<dependency>
  <groupId>commons-net</groupId>
  <artifactId>commons-net</artifactId>
  <version>3.11.1</version>
</dependency>

2. Connect to an FTP or FTPS server.

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

private static final String FTP_SERVER = "127.0.0.1";
//Default FTPS port is 990
//Default FTP port is 21
private static final int FTP_PORT = 21; 
private static final String FTP_USER = "nithidol.v";
private static final String FTP_PASSWORD = "password";

private void connect(){
      FTPClient ftpClient = new FTPClient();
      try {
          // Connect to the FTP server
          ftpClient.connect(FTP_SERVER, FTP_PORT);
          ftpClient.login(FTP_USER, FTP_PASSWORD);

          // Set file transfer type to binary
          ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
      } catch (IOException e) {
          e.printStackTrace();
      } finally {
          try {
              // Disconnect from the FTP server
              if (ftpClient.isConnected()) {
                  ftpClient.logout();
                  ftpClient.disconnect();
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }

3. List files from the FTP server.

private static void listFiles(FTPClient ftpClient) throws IOException {
    System.out.println("Listing files on the FTP server:");
    FTPFile[] files = ftpClient.listFiles();
    for (FTPFile file : files) {
        System.out.println(file.getName());
    }
}

4. Download the file from the FTP server.

private void downloadFile(FTPClient ftpClient, String remoteFilePath, String localFilePath) throws IOException {
    File localFile = new File(localFilePath);
    OutputStream outputStream = new FileOutputStream(localFile);
    ftpClient.retrieveFile(remoteFilePath, outputStream);
    System.out.println("File downloaded successfully.");
}

5. Upload the file from the FTP server.

private void uploadFile(FTPClient ftpClient, String localFilePath, String remoteFilePath) throws IOException {
    File localFile = new File(localFilePath);
    InputStream inputStream = new FileInputStream(localFile);
    ftpClient.storeFile(remoteFilePath, inputStream);
    System.out.println("File uploaded successfully.");
}

6. Rename the file on the FTP server.

private static renameFile(FTPClient ftpClient, String oldName, String newName) throws IOException {
    boolean success = ftpClient.rename(oldName, newName);
    if (success) {
        System.out.println("File renamed successfully.");
    } else {
        System.out.println("Failed to rename file.");
    }
}

7. Delete the file from the FTP server.

private static void deleteFile(FTPClient ftpClient, String filePath) throws IOException {
    boolean success = ftpClient.deleteFile(filePath);

    if (success) {
        System.out.println("File deleted successfully.");
    } else {
        System.out.println("Failed to delete file.");
    }
}

8. Resume files from the FTP server.

 private void resume(FTPClient ftpClient, String remoteFilePath, String localFilePath) throws IOException {
    // Check if the server supports resume
    if (FTPReply.isPositiveIntermediate(ftpClient.getReplyCode())) {
        // Set the restart offset to the size of the partially downloaded file
        long restartOffset = getLocalFileSize("downloaded-file.txt");
        ftpClient.setRestartOffset(restartOffset);
    }

    FileOutputStream localFile = new FileOutputStream(localFilePath, true); // Set append mode to true
    boolean success = ftpClient.retrieveFile(remoteFilePath, localFile);
    localFile.close();

    if (success) {
        System.out.println("File downloaded successfully.");
    } else {
        System.out.println("Failed to download file.");
    }
}

private static long getLocalFileSize(String filePath) {
    File file = new File(filePath);
    return file.exists() ? file.length() : 0;
}

Java SFTP connection with JSch

Java SFTP client example

The developer can use the JSCH library to connect to the SFTP server.

1. Add Maven dependency.

<!-- https://mvnrepository.com/artifact/com.github.mwiede/jsch -->
<dependency>
    <groupId>com.github.mwiede</groupId>
    <artifactId>jsch</artifactId>
    <version>0.2.19</version>
</dependency>

2. Connect to the SFTP server.

import com.jcraft.jsch.*;
import java.io.File;
import java.util.Vector;

private static ChannelSftp setupJsch() throws JSchException {
    String server = "127.0.0.1";
    int port = 22;
    String user = "nithidl.v";
    String pass = "pass";
    JSch jsch = new JSch();
    jsch.setKnownHosts("../.ssh/known_hosts");
    Session jschSession = jsch.getSession(user, server, port);
    jschSession.setPassword(pass);
    System.out.println("#connect begin.");
    jschSession.connect();
    System.out.println("#connect end.");
    return (ChannelSftp) jschSession.openChannel("sftp");
}

3. List files from the FTP server.

private void list() throws JSchException, SftpException {
    ChannelSftp channelSftp = setupJsch();
    channelSftp.connect();
    String remoteFile = "/path/folder/";
    Vector<ChannelSftp.LsEntry> list = channelSftp.ls(remoteFile);
    for (ChannelSftp.LsEntry entry : list) {
        list.forEach((l)->System.out.println("FileNameOrFolderName="+l.getFilename()));
    }
    channelSftp.exit();
    System.exit(0);
}

4. Download files from the server.

private void download() throws JSchException, SftpException {
    ChannelSftp channelSftp = setupJsch();
    channelSftp.connect();
    String serverPath = "/path/folder/demo.txt";
    String localPath = "c:/demo.txt";
    channelSftp.get(serverPath, localPath);
    channelSftp.exit();
    System.exit(0);
}

5. Upload files from the server.

private void upload() throws JSchException, SftpException {
    ChannelSftp channelSftp = setupJsch();
    channelSftp.connect();
    String serverPath = "/path/folder/demo.txt";
    String localPath = "c:/demo.txt";
    channelSftp.put(localPath, serverPath);
    channelSftp.exit();
    System.exit(0);
}

6. Delete files from the server.

private void delete() throws JSchException, SftpException {
    ChannelSftp channelSftp = setupJsch();
    channelSftp.connect();
    String serverPath = "/path/folder/demo.txt";
    channelSftp.rm(serverPath);
    channelSftp.exit();
    System.exit(0);
}

7. Rename files from the server.

private void rename() throws JSchException, SftpException {
    ChannelSftp channelSftp = setupJsch();
    channelSftp.connect();
    String oldFilePath = "/path/folder/file.txt";
    String newFilePath = "/path/folder/file2.txt";
    channelSftp.rename(oldFilePath, newFilePath);
    channelSftp.exit();
    System.exit(0);
}

Operational note: avoid killing the JVM after a transfer

System.exit() is acceptable in a small command-line demo, but it is dangerous inside a server process, scheduler, or Spring Boot worker. In production code, close the FTP or SFTP channel, return a failure result, and let the caller decide whether to retry, alert, or stop the job.

  • 0: Indicates normal termination.
  • Non-zero values (e.g., 1): Indicate abnormal termination or errors.

Production checklist for Java file transfers

Use plain FTP only when the network boundary is trusted and the risk is understood. For most production systems, prefer SFTP or FTPS, set timeouts, validate file size after transfer, write downloads to temporary paths before renaming them, and treat retries as part of the design instead of an afterthought.

  • Prefer SFTP when teams already operate SSH keys and host-level access controls.
  • Prefer FTPS when the server ecosystem is FTP-based but encryption is required.
  • Always configure connect, read, and transfer timeouts.
  • Make file processing idempotent so retries do not duplicate downstream work.

For more backend reliability reading, continue with JobRunr with Spring Boot and Spring Boot Resilience4j TimeLimiter.

This article was originally published on Medium.