Frequently Asked Questions

Job Execution vs Job Instance?
Job instance is like blueprint. Job Execution is actual job.

Job instance is Unique by Job Name + Parameters
Job Execution need not be Unique incase of failed Execution

Why Job Instance should be Unique?
Spring Batch uses job name + parameters to identify a job instance. This prevents accidental duplicate processing—especially important in financial, ETL, or reporting systems. If you try to run the same job with the same parameters again – Spring Batch sees that the Job Instance already completed.- It prevents re-execution to avoid duplicate processing or unintended side effects (e.g., writing the same data twice).

Why Job Execution may not be Unique?
If a job execution fails, you can restart it with the same parameters. Spring Batch will:

  1. Reuse the same job instance
  2. Create a new job execution
  3. Resume from where it left off (if configured

Let’s say you run a job called “dataImportJob” with parameters {file=data.csv}:

  1. ❌First run Fails → JobInstance ID = 101, JobExecution ID = 201
  2. ✅You retry → JobExecution ID = 202 (still linked to JobInstance ID = 101)
  3. ✅You run with {file=data_v2.csv} → New JobInstance ID = 102, JobExecution ID = 203

So: JobInstance 101 → has executions 201 and 202, JobInstance 102 → has execution 203

Why This Matters

  1. You can track retries and failures per job instance.
  2. You can restart a failed job execution without creating a new instance.
  3. You can query historical runs by instance or execution ID.

How Job Instance and Execution are Stored in DB?
JobInstance, JobExecution, JobParameters, and StepExecution map to BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS, and BATCH_STEP_EXECUTION respectively
BATCH_JOB_EXECUTION_PARAMS table always refers parameters at Job Execution Level rather than Job Instance Level. This is done so each execution can retain a complete and independent record of the inputs used—even if those parameters are the same across multiple executions of the same job instance. Though the identity of a job instance is based on its parameters storing them only at the instance level would create a few limitations.

Executions wouldn’t have their own copy of parameters, making it harder to audit or debug individual runsRetries or restarts would have no way to track what parameters were used during each attempt.Execution-level metadata (like timestamps, exit codes, and logs) would be disconnected from the parameters that triggered them.

Difference between Tasklet Step and Chunk Oriented Step?
Tasklet – Executes one discrete action (e.g., delete a file, call a stored procedure) and ideal for non-repetitive tasks. I.E. File cleanup, Sending emails, Running shell scripts, Database maintenance
Chunk – processing is designed for handling large datasets by breaking them into manageable pieces.Uses ItemReader, ItemProcessor, and ItemWriter.Commits after processing a defined number of items. I.E. Reading from a database or file and writing to another source, High-volume data processing

If you’re building a job that just needs to do one thing—like clean up a directory—go with a Tasklet. If you’re processing thousands of records, Chunk-Oriented is your best bet.

JobConfig.java

    .
    .
    .
    @Bean
    public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobBuilder("job", jobRepository)
                .incrementer(new RunIdIncrementer())
                .start(simpleTaskletStep(jobRepository, transactionManager))
                .next(simpleChunkStep(jobRepository, transactionManager))
                .build();
    }

    @Bean
    public Step simpleTaskletStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        StepBuilder stepBuilderOne = new StepBuilder("Tasklet Oriented Step", jobRepository);
        return stepBuilderOne
                .tasklet(simpleTaskLet(), transactionManager)
                .build();
    }

    @Bean
    public Tasklet simpleTaskLet(){
        return (StepContribution contribution, ChunkContext chunkContext) -> {
            return RepeatStatus.FINISHED;
        };
    }

    @Bean
    public Step simpleChunkStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        StepBuilder stepBuilderOne = new StepBuilder("Chunk Oriented Step", jobRepository);
        return stepBuilderOne
                .chunk(3, transactionManager)
                .reader(simpleJobReader)
                .processor(simpleJobProcessor)
                .writer(simpleJobWriter)
                .build();
    }

Difference between ItemReader and ItemWriter?
ItemReader reads one item at a time and called repeatedly until it returns null, signaling no more data. ItemWriter Writes a list of processed items to a destination (like a file, database, or message queue) and it is called once per chunk with a list of items.

🔄 ItemReader brings the ingredients to the kitchen,
🧑‍🍳 ItemProcessor cooks them,
📦 ItemWriter packs and delivers the final dish

Why ItemReader reads each row one after another whereas ItemWriter writes in chunk?
ItemReader is to process each item individually—transforming, validating, or enriching it—before it’s added to a chunk for writing whereas ItemWriter on other hand writes in bulk as it is faster and reduces overhead—especially for I/O operations like database inserts or file writes.

How to prevent spring batch jobs run on start?

.
.
spring.batch.job.enabled=false
.
.

How it Works

  1. We can define the Size of Jobreader and Job Processor. In other words the no of records to be processed could be set using Jobreader and job processor
  2. We dont have control over Jobwriter. Jobreader and JobProcessor honors chunk size

SimpleJobReader.java

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;

@Component
public class SimpleJobReader implements ItemReader<Integer> {
    List<Integer> arrNum = Arrays.asList(1,2,3,4,5,6,7,8,9);
    int chunkSize = 3;
    int index = 0;

    @Override
    public Integer read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        System.out.println("Inside Job Reader");
        if(index < arrNum.size()){
            int num = arrNum.get(index);
            index++;
            return num;
        }
        index = 0;

        return null;
    }
}

SimpleJobProcessor.java

import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

@Component
public class SimpleJobProcessor implements ItemProcessor<Integer, Long> {
    @Override
    public Long process(Integer item) throws Exception {
        System.out.println("Inside Job Processor");
        return Long.valueOf(item);
    }
}

SimpleJobWriter.java

import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;

@Component
public class SimpleJobWriter implements ItemWriter<Long> {
    @Override
    public void write(Chunk<? extends Long> items) throws Exception {
        System.out.println("Inside Job Writer");
        items.getItems().forEach(System.out::println);
    }
}

SimpleJobWriter.java

@Configuration
public class BatchConfig {
    @Autowired
    ItemReader simpleJobReader;

    @Autowired
    ItemWriter simpleJobWriter;

    @Autowired
    ItemProcessor simpleJobProcessor;

    @Bean
    public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobBuilder("job", jobRepository)
                .incrementer(new RunIdIncrementer())
                .start(simpleTaskletStep(jobRepository, transactionManager))
                .next(simpleChunkStep(jobRepository, transactionManager))
                .build();
    }

    @Bean
    public Step simpleChunkStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        StepBuilder stepBuilderOne = new StepBuilder("Chunk Oriented Step", jobRepository);
        return stepBuilderOne
                .chunk(3, transactionManager)
                .reader(simpleJobReader)
                .processor(simpleJobProcessor)
                .writer(simpleJobWriter)
                .build();
    }

    @Bean
    public Step simpleTaskletStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        StepBuilder stepBuilderOne = new StepBuilder("Tasklet Oriented Step", jobRepository);
        return stepBuilderOne
                .tasklet(simpleTaskLet(), transactionManager)
                .build();
    }

    @Bean
    public Tasklet simpleTaskLet(){
        return (StepContribution contribution, ChunkContext chunkContext) -> {
            return RepeatStatus.FINISHED;
        };
    }
}

Output

Inside Job Reader
Inside Job Reader
Inside Job Reader
Inside Job Processor
Inside Job Processor
Inside Job Processor
Inside Job Writer
1
2
3
Inside Job Reader
Inside Job Reader
Inside Job Reader
Inside Job Processor
Inside Job Processor
Inside Job Processor
Inside Job Writer
4
5
6
Inside Job Reader
Inside Job Reader
Inside Job Reader
Inside Job Processor
Inside Job Processor
Inside Job Processor
Inside Job Writer
7
8
9
Inside Job Reader