Spring Batch Download Files from SFTP Server Using Tasklet (Complete Guide)

Spring Batch Download Files from SFTP Server Using Tasklet (Complete Guide)

In enterprise applications, data exchange heavily relies on Secure File Transfer Protocol (SFTP). Banks, insurance companies, logistics systems, and reporting platforms frequently drop daily transaction files onto an SFTP server for downstream systems to consume.

Instead of writing manual shell scripts or unmanaged Java loops to download these files, we can automate the entire process within a controlled, transaction-aware environment using Spring Batch and the JSch library.

What You Will Learn in This Guide:
✔ The architectural difference between a Tasklet and Chunk processing.
✔ How to implement a complete Tasklet to connect to SFTP using JSch.
✔ How to download multiple files automatically and clean up the remote server.
✔ How to safely trigger this batch job using a REST API.
✔ Crucial security configurations for production environments.

๐Ÿ“บ Watch the Complete Implementation Live!
If you prefer visual learning, watch the full step-by-step video tutorial where we build this exact Spring Batch pipeline from scratch to download files from SFTP Server.

▶ Watch the Full Spring Batch Download Video Tutorial

1. Why Tasklet Instead of Chunk Processing?

Spring Batch offers two primary ways to process data: Chunk-oriented processing and Tasklets. Knowing when to use which is a common senior developer interview question.

FeatureTasklet (Used Here)Chunk Processing
Best ForSingle, distinct operational tasks (e.g., executing a script, downloading a file, cleaning up directories).Iterating over thousands of records (e.g., reading a CSV row by row, processing, and inserting into a DB).
ExecutionExecutes the execute() method exactly once (unless configured to repeat).Loops continuously (Read → Process → Write) until the data source is exhausted.
ComplexityHighly procedural and simple to implement.Requires configuring an ItemReader, ItemProcessor, and ItemWriter.

Because downloading a batch of files from an SFTP server is a distinct, step-by-step procedure rather than a record-by-record transformation, a Tasklet is the architecturally correct choice.

2. Maven Dependencies (pom.xml)

To build this, we need Spring Batch for the job infrastructure, Spring Web for our REST trigger, and JSch (Java Secure Channel). JSch is a pure Java implementation of SSH2, allowing us to connect to an SFTP server and manipulate files programmatically.

<dependencies>
    <!-- Spring Boot Batch & Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- JSch for SFTP Connection -->
    <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.55</version>
    </dependency>
    
    <!-- Oracle DB for Batch Metadata -->
    <dependency>
        <groupId>com.oracle.database.jdbc</groupId>
        <artifactId>ojdbc11</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

3. Application Properties

Define your SFTP credentials and paths in your application.properties.

# Crucial: Prevent job from auto-running on startup
spring.batch.job.enabled=false

# SFTP Configuration
sftp.host=YOUR_SFTP_HOST
sftp.port=22
sftp.username=YOUR_USERNAME
sftp.password=YOUR_PASSWORD
sftp.remote.dir.path=/home/reports
sftp.local.dir.path=F:/reports
Architectural Rule: We explicitly set spring.batch.job.enabled=false. If omitted, Spring Boot will automatically launch the job the second the application starts, which defeats the purpose of triggering it manually via our REST controller.

4. The Complete SFTP Download Tasklet

This is the heart of our application. We implement the Tasklet interface. The execute() method establishes an SSH session, opens an SFTP channel, lists the files in the remote directory, downloads them to our local machine, and then deletes the remote copy to prevent duplicates.

import com.jcraft.jsch.*;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.util.Vector;

@Slf4j
@Component
public class SftpDownloadTasklet implements Tasklet {

    @Value("${sftp.host}") private String host;
    @Value("${sftp.port}") private int port;
    @Value("${sftp.username}") private String username;
    @Value("${sftp.password}") private String password;
    @Value("${sftp.remote.dir.path}") private String remoteDirPath;
    @Value("${sftp.local.dir.path}") private String localDirPath;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        JSch jSch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;

        try {
            log.info("Establishing SSH Session to SFTP Server...");
            session = jSch.getSession(username, host, port);
            session.setPassword(password);
            
            // WARNING: For development only. See Production Security note below.
            session.setConfig("StrictHostKeyChecking", "no"); 
            session.connect();

            log.info("Opening SFTP Channel...");
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();

            // List all files in the remote directory
            Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remoteDirPath);
            
            for (ChannelSftp.LsEntry file : files) {
                // Ignore system directories "." and ".."
                if (!file.getFilename().equals(".") && !file.getFilename().equals("..")) {
                    String remoteFilePath = remoteDirPath + "/" + file.getFilename();
                    String localFilePath = localDirPath + "/" + file.getFilename();

                    log.info("Downloading file: {}", file.getFilename());
                    channelSftp.get(remoteFilePath, localFilePath);

                    log.info("Deleting remote file to prevent duplicate processing: {}", file.getFilename());
                    channelSftp.rm(remoteFilePath);
                }
            }
            
            log.info("SFTP Download Tasklet completed successfully.");
            return RepeatStatus.FINISHED;
            
        } finally {
            // Guarantee resource cleanup to prevent memory leaks and server exhaustion
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
            }
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
        }
    }
}

5. Job and Step Configuration

Now we wire our `Tasklet` into a Spring Batch `Step`, and place that step inside a `Job`.

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class BatchConfig {

    @Bean
    public Step sftpDownloadStep(JobRepository jobRepository, 
                                 PlatformTransactionManager transactionManager, 
                                 SftpDownloadTasklet tasklet) {
        return new StepBuilder("sftpDownloadStep", jobRepository)
                .tasklet(tasklet, transactionManager)
                .build();
    }

    @Bean
    public Job sftpJob(JobRepository jobRepository, Step sftpDownloadStep) {
        return new JobBuilder("sftpDownloadJob", jobRepository)
                .start(sftpDownloadStep)
                .build();
    }
}

6. Triggering the Job via REST API

We use a @RestController to trigger the batch job on demand. Notice that we inject a dynamic executionTime into the JobParametersBuilder. Spring Batch identifies unique job instances by their parameters. If you try to run a job twice with the exact same parameters, Spring Batch will throw a JobInstanceAlreadyCompleteException.

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/jobs")
public class JobController {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job sftpJob;

    @GetMapping("/trigger-download")
    public ResponseEntity<String> triggerSftpDownload() throws Exception {
        
        JobParameters jobParameters = new JobParametersBuilder()
                .addLong("executionTime", System.currentTimeMillis()) // Ensures uniqueness
                .toJobParameters();

        jobLauncher.run(sftpJob, jobParameters);

        return ResponseEntity.ok("SFTP Download Job Triggered Successfully");
    }
}

7. Production Security Discussion

In our code, we used session.setConfig("StrictHostKeyChecking", "no");. While this is acceptable for local development and testing, it is a severe security risk in production. It makes your application vulnerable to Man-in-the-Middle (MITM) attacks because it blindly accepts any server's identity.

The Production Fix: You should always verify the SFTP server's identity using a `known_hosts` file.

// Production Standard Configuration
jSch.setKnownHosts("/path/to/your/known_hosts");
session = jSch.getSession(username, host, port);
// StrictHostKeyChecking defaults to "yes" securely

Conclusion

Spring Batch Tasklets combined with the JSch library provide a highly procedural, straightforward solution for automating SFTP downloads. By wrapping this logic inside a Spring Batch Step, you gain access to the full suite of batch monitoring, execution history (via metadata tables), and transaction safety that manual scripts simply cannot provide.

๐Ÿ”„ Deepen Your Spring Batch Knowledge

๐Ÿงฑ Spring Batch Core Components

Understand how ItemReader, ItemProcessor, and ItemWriter work together once your SFTP files are downloaded.

๐Ÿ”„ Spring Batch ItemProcessor Example

Apply transformation and formatting logic to the CSV files you just downloaded from the SFTP server.

๐Ÿ” CSV to Database with Spring Batch

Learn how to take your newly downloaded local files and parse them into your Oracle database.

๐Ÿšซ Skip Policy & Error Handling

Handle read failures and formatting errors gracefully without crashing the entire batch process.

๐Ÿ”€ Conditional Flow in Spring Batch Jobs

Control job execution paths based on whether the SFTP download Tasklet succeeds or fails.

๐Ÿงต Multithreaded Step in Spring Batch

Improve performance by parsing your downloaded CSV files across parallel processing threads.