How do I transfer files with resume support over SFTP using JSch?

When transferring files over SFTP using JSch, resuming partially transferred files (either uploads or downloads) can be implemented by handling offsets for files that are already partially transferred.

This guide explains how to:

  • Resume downloads by continuing from the last transferred byte of a local file.
  • Resume uploads by appending to a remote file.

Handling Download with Resume Support

To resume a download:

  1. Check the current size of the local file.
  2. Skip already downloaded bytes from the remote file using InputStream.skip().
  3. Append remaining content to the local file.

Code for Resuming Download

package org.kodejava.jsch;

import com.jcraft.jsch.*;
import java.io.*;

public class SFTPResumeDownload {

   public static void main(String[] args) {
      String host = "sftp.example.com";
      String username = "user";
      String password = "password";
      String localFile = "local/path/to/file.txt";
      String remoteFile = "/remote/path/to/file.txt";

      JSch jsch = new JSch();
      Session session = null;
      ChannelSftp sftpChannel = null;

      try {
         // Setup SFTP connection
         session = jsch.getSession(username, host, 22);
         session.setPassword(password);
         session.setConfig("StrictHostKeyChecking", "no"); // Disable key checking
         session.connect();

         Channel channel = session.openChannel("sftp");
         channel.connect();
         sftpChannel = (ChannelSftp) channel;

         // Resume download logic
         File file = new File(localFile);
         long localFileSize = file.exists() ? file.length() : 0;
         long remoteFileSize = sftpChannel.lstat(remoteFile).getSize();

         if (localFileSize >= remoteFileSize) {
            System.out.println("File already fully downloaded.");
            return;
         }

         try (InputStream inputStream = sftpChannel.get(remoteFile);
              OutputStream outputStream = new FileOutputStream(file, true)) {
            inputStream.skip(localFileSize); // Skip downloaded portion

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
               outputStream.write(buffer, 0, bytesRead);
            }

            System.out.println("Download resumed and completed.");
         }
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         if (sftpChannel != null) sftpChannel.disconnect();
         if (session != null) session.disconnect();
      }
   }
}

Handling Upload with Resume Support

To resume an upload:

  1. Check the size of the remote file.
  2. Skip already uploaded bytes from the local file.
  3. Use the ChannelSftp.APPEND flag to append remaining bytes to the remote file.

Code for Resuming Upload

public static void resumeUpload(ChannelSftp sftpChannel, String localFile, String remoteFile) throws SftpException, IOException {
    File file = new File(localFile);
    long remoteFileSize = 0;

    try {
        remoteFileSize = sftpChannel.lstat(remoteFile).getSize(); // Check remote file size
    } catch (SftpException e) {
        System.out.println("Remote file does not exist. Starting upload from the beginning.");
    }

    System.out.println("Resuming upload from byte: " + remoteFileSize);

    try (InputStream inputStream = new FileInputStream(file)) {
        inputStream.skip(remoteFileSize); // Skip already uploaded bytes

        // Append mode upload
        sftpChannel.put(inputStream, remoteFile, ChannelSftp.APPEND);
        System.out.println("Resume upload completed.");
    }
}

Explanation of Key Steps

  1. Session Setup:
    • A secure session is established with the SFTP server using user credentials.
    • StrictHostKeyChecking is disabled for simplicity, but proper key validation is recommended for production.
  2. Resume Logic:
    • Download: The remote file is read as an InputStream, skipping already downloaded bytes. The local file is opened in append mode.
    • Upload: The local file is read as an InputStream, skipping already uploaded bytes, and the put method with ChannelSftp.APPEND is used to continue the upload.
  3. Error Handling:
    • If the remote file or local file does not exist, appropriate error handling ensures either the upload/download starts from the beginning or exits gracefully.
  4. File Integrity: To ensure file integrity, consider validating the file with hash checks or checksums after transfer.

Notes

  • Increase the buffer size (byte[] buffer = new byte[1024]) for better performance for larger files.
  • Consider implementing retries or reconnect logic if the SFTP session disconnects during a transfer.
  • Always confirm proper permissions for writing to the destination and reading from the source.

Conclusion

The above solution demonstrates how to implement resumable file transfer via SFTP using JSch. It ensures efficient and reliable file transfers by avoiding redundant retransmission of already transferred data.


Maven Dependencies

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

Maven Central

How do I download a file from an SSH server using JSch SFTP?

To download a file from an SSH server using JSch SFTP, you can use the ChannelSftp class from the JSch library. Below is an example of how to achieve this:

Code Example: Downloading a file using JSch SFTP

The JSch library is used to establish an SSH connection to an SFTP server and transfer files. Here’s a step-by-step guide:

package org.kodejava.jsch;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

import java.io.FileOutputStream;

public class SFTPDownloadExample {
   public static void main(String[] args) {
      String sftpHost = "sftp.example.com";
      int sftpPort = 22;
      String sftpUser = "username";
      String sftpPassword = "password";
      String remoteFile = "/path/to/remote/file.txt";
      String localFile = "local-file-path.txt";

      Session session = null;
      Channel channel = null;
      ChannelSftp channelSftp = null;

      try {
         // Initialize JSch
         JSch jsch = new JSch();

         // Create session
         session = jsch.getSession(sftpUser, sftpHost, sftpPort);

         // Set the password
         session.setPassword(sftpPassword);

         // Configure strict host key checking (optional)
         session.setConfig("StrictHostKeyChecking", "no");

         // Connect the session
         System.out.println("Connecting to the SFTP server...");
         session.connect();
         System.out.println("Connected successfully.");

         // Open the SFTP channel
         channel = session.openChannel("sftp");
         channel.connect();
         channelSftp = (ChannelSftp) channel;

         // Download the file
         System.out.println("Downloading file...");
         channelSftp.get(remoteFile, localFile);
         System.out.println("File downloaded to: " + localFile);

      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         // Clean up resources
         if (channelSftp != null) {
            channelSftp.disconnect();
         }
         if (channel != null) {
            channel.disconnect();
         }
         if (session != null) {
            session.disconnect();
         }
      }
   }
}

Explanation of the Code

  1. Set up connection details: Set the SFTP server’s host, port, username, password, the path to the remote file, and the local file.
  2. JSch initialization:
    • Create a Session object with user credentials (host, port, username, and password).
    • Use session.setConfig("StrictHostKeyChecking", "no") to skip host key verification (use for testing; not recommended for production due to security risks).
    • Connect to the server using session.connect().
  3. Open the SFTP channel:
    • Open a channel to the server with session.openChannel("sftp").
    • Cast the channel to ChannelSftp and connect.
  4. Download the file:
    • Use ChannelSftp.get(remoteFile, localFile) to download the remote file to the specified local path.
  5. Clean up resources:
    • Disconnect the ChannelSftp, Channel, and Session objects to free up resources.

Output Example

If successful, the program outputs the following:

Connecting to the SFTP server...
Connected successfully.
Downloading file...
File downloaded to: local-file-path.txt

Note

  • If your SFTP server uses public/private keys, you can use jsch.addIdentity("path/to/private_key") before initiating the session instead of a password.
  • Always handle exceptions and manage resources carefully in a real-world application to ensure robustness.

This example should work to download files via SFTP in Java.


Maven Dependencies

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

Maven Central

How do I handle file uploads using Jakarta Servlet 6.0+?

In Jakarta Servlet 6.0+, file uploads can be handled efficiently using the native APIs provided by the servlet specifications. Here’s a step-by-step guide:

1. Enable Multipart Config for the Servlet

To handle file uploads, the servlet must be annotated with @MultipartConfig, which enables support for handling multipart/form-data requests. You can configure parameters such as maximum file size, total request size, and file location.

Example:

package org.kodejava.servlet;

import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;

import java.io.File;
import java.io.IOException;

@WebServlet("/upload")
@MultipartConfig(
        fileSizeThreshold = 1024 * 1024 * 2, // 2MB. Files above this size will be written to disk.
        maxFileSize = 1024 * 1024 * 10,      // 10MB. Maximum size for a single uploaded file.
        maxRequestSize = 1024 * 1024 * 50,   // 50MB. Maximum size of the entire request.
        location = "/tmp"                    // Temporary directory for uploaded files.
)
public class FileUploadServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {

    // Ensure the request is indeed a multipart request
    if (!request.getContentType().startsWith("multipart/form-data")) {
      response.getWriter().println("Invalid request type");
      return;
    }

    // Retrieve the part associated with the upload form field
    Part filePart = request.getPart("file");  // "file" is the form field name
    if (filePart != null) {
      // Retrieve file information
      String fileName = extractFileName(filePart);
      long fileSize = filePart.getSize(); // Size of the file in bytes

      // Define file path for saving
      String uploadDir = getServletContext().getRealPath("") + File.separator + "uploads";
      File uploadDirFile = new File(uploadDir);
      if (!uploadDirFile.exists()) {
        uploadDirFile.mkdirs(); // Create directories if they don't exist
      }
      String filePath = uploadDir + File.separator + fileName;

      // Write the uploaded file to the target directory
      filePart.write(filePath);

      // Respond back to the client
      response.getWriter().println("File uploaded successfully to: " + filePath);
    } else {
      response.getWriter().println("File upload failed. Missing file part.");
    }
  }

  // Utility method to extract the file name from the HTTP header
  private String extractFileName(Part part) {
    String contentDisposition = part.getHeader("content-disposition");
    for (String content : contentDisposition.split(";")) {
      if (content.trim().startsWith("filename")) {
        return content.substring(content.indexOf("=") + 2, content.length() - 1); // Extract filename
      }
    }
    return "unknown";
  }
}

2. Create a Front-End Form

Ensure your front-end has a form with the enctype="multipart/form-data" attribute.

Example HTML Form:

<!DOCTYPE html>
<html>
<head>
    <title>File Upload</title>
</head>
<body>
    <h2>Upload a File</h2>
    <form method="post" action="/upload" enctype="multipart/form-data">
        <label for="file">Choose a file:</label>
        <input type="file" name="file" id="file" required>
        <br>
        <button type="submit">Upload</button>
    </form>
</body>
</html>

3. Explanation of Key Parts in the Code

  • @MultipartConfig Annotation:
    • fileSizeThreshold: Files larger than this threshold are written to disk instead of memory.
    • maxFileSize: The maximum size allowed for a single file.
    • maxRequestSize: The maximum size allowed for the entire request (including all parts).
    • location: Directory where files are stored temporarily. Files exceeding the threshold are saved here.
  • Part Object:
    • Represents a part in a multipart request. You can use it to get the file content, headers, and write it to a file.
  • extractFileName() Utility Function:
    • Extracts the file name from the content-disposition header of the Part.

4. File Storage Directory

Ensure the target directory exists (or is created) where the uploaded files will be stored. The example uses a directory inside your web application (uploads folder).
For production setups, consider storing files in external directories to avoid packaging issues.

5. Additional Considerations

  • Validation:
    • Validate the uploaded file (e.g., file type, size) for security reasons.
  • Error Handling:
    • Use appropriate exception handling for cases like large files (IllegalStateException) or I/O errors.
  • Security:
    • Always sanitize the file name and restrict file types to prevent malicious uploads.

6. Dependencies

If you’re using a Jakarta Servlet container (e.g., Apache Tomcat 10+), make sure the servlet API dependency exists in your pom.xml (or equivalent build file):

With this setup, your servlet will successfully handle file uploads in Jakarta Servlet 6.0+! Let me know if you have further questions or need additional details

Maven dependencies

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.1.0</version>
    <scope>provided</scope>
</dependency>

Maven Central

How do I do multipart upload using HttpClient?

This example demonstrates how to do multipart upload using the Apache HttpClient library. In this example we upload a single file. We start by creating an object of the file to be uploaded. The FileBody represent the binary body part of the file.

Next, prepare the HttpEntity object by create an instance of MultipartEntityBuilder. Add parts to this object, in this case we add the fileBody. We can add multiple part to this object as the name says. It can be string, file, etc. as we do in a normal web form.

The build() method of the builder object finalize the entity creation and return us the HttpEntity object. To send / upload to server we create an HttpPost request and set the entity to be posted. Finally, the execute() method of the HttpClient object send the multipart object to server.

package org.kodejava.apache.http;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import java.io.File;
import java.io.IOException;

public class HttpPostMultipartExample {
    public static void main(String[] args) {
        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
            File file = new File("data.zip");
            FileBody fileBody = new FileBody(file, ContentType.DEFAULT_BINARY);

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
            builder.addPart("file", fileBody);
            HttpEntity entity = builder.build();

            HttpPost request = new HttpPost("http://localhost:8080/upload");
            request.setEntity(entity);
            client.execute(request);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

To receive the file on the server you can take a look at the servlet code in the following example: How do I create a web based file upload?.

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.14</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpmime</artifactId>
        <version>4.5.14</version>
    </dependency>
</dependencies>

Maven Central Maven Central

How do I create a web based file upload?

This example using the Apache Commons FileUpload library to create a simple application for uploading files. The program is divided into two parts, a form using JSP and a servlet for handling the upload process. To run the sample you need to download the Commons FileUpload and Commons IO get the latest stable version.

Commons FileUpload Demo

Commons FileUpload Demo

The first step is to create the upload form. The form contains two fields for selecting file to be uploaded and a submit button. The form should have an enctype attribute and the value is multipart/form-data. We use a post method and the submit process is handled by the upload-servlet as defined in the action attribute.

<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <title>File Upload</title>
</head>

<body>
<h1>File Upload Form</h1>
<hr/>
<form action="${pageContext.request.contextPath}/upload-servlet"
      method="post" enctype="multipart/form-data">

    <fieldset>
        <legend>Upload File</legend>
        <label for="filename_1">File: </label>
        <input id="filename_1" type="file" name="filename_1" size="50"/><br/>
        <label for="filename_2">File: </label>
        <input id="filename_2" type="file" name="filename_2" size="50"/><br/>
        <br/>
        <input type="submit" value="Upload File"/>
    </fieldset>

</form>
</body>
</html>

The second step is to create the servlet. The doPost method checks to see if the request contains a multipart content. After that we create a FileItemFactory, in this example we use the DiskFileItemFactory which is the default factory for FileItem. This factory creates an instance of FileItem and stored it either in memory or in a temporary file on disk depending on its content size.

The ServletFileUpload handles multiple files upload that we’ve specified in the form above sent using the multipart/form-data encoding type. The process of storing the data is determined by the FileItemFactory passed to the ServletFileUpload class.

The next steps is to parse the multipart/form-data stream by calling the ServletFileUpload.parseRequest(HttpServletRequest request) method. The parse process return a list of FileItem. After that we iterate on the list and check to see if FileItem representing an uploaded file or a simple form field. If it is represents an uploaded file we write the FileItem content to a file.

So here is the FileUploadDemoServlet.

package org.kodejava.commons.fileupload;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.Serial;
import java.util.List;

@WebServlet(urlPatterns = "/upload-servlet")
public class FileUploadDemoServlet extends HttpServlet {
    @Serial
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);

        if (isMultipart) {
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);

            try {
                List<FileItem> items = upload.parseRequest(request);
                for (FileItem item : items) {
                    if (!item.isFormField()) {
                        String fileName = item.getName();

                        String root = getServletContext().getRealPath("/");
                        File path = new File(root + "/uploads");
                        if (!path.exists()) {
                            boolean status = path.mkdirs();
                        }

                        File uploadedFile = new File(path + "/" + fileName);
                        System.out.println(uploadedFile.getAbsolutePath());
                        item.write(uploadedFile);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.5</version>
    </dependency>
</dependencies>

Maven Central Maven Central