Master Spring Batch: From Basics to Advanced Job Configurations

This article provides a comprehensive guide to Spring Batch, covering its purpose, supported business scenarios, core components and interfaces, Maven setup, sample job definitions, parallel execution, decision flows, nested jobs, data reading and writing, validation processing, and REST‑based job scheduling, all illustrated with complete code examples.

Architect's Guide
Architect's Guide
Architect's Guide
Master Spring Batch: From Basics to Advanced Job Configurations

1. Spring Batch Introduction

Spring Batch is a lightweight, comprehensive batch‑processing framework designed to support robust batch applications essential for daily enterprise operations. It builds on Spring Framework features such as productivity, POJO‑based development, and ease of use, while allowing access to advanced enterprise services when needed.

Spring Batch is not a scheduling framework; it is intended to be used together with external schedulers like Quartz, Tivoli, or Control‑M.

2. Business Scenarios

Spring Batch supports the following scenarios:

Periodic batch submission.

Concurrent batch processing: parallel job execution.

Stage‑wise, message‑driven enterprise processing.

Large‑scale parallel batch processing.

Manual or scheduled restart after failure.

Ordered processing of related steps (extensible to workflow‑driven batches).

Partial processing: skipping records (e.g., during rollback).

Whole‑batch transactions suitable for small batches or when stored procedures/scripts already exist.

3. Fundamentals

3.1 Overall Architecture

Key components (from the official documentation):

JobRepository – provides persistence for all job‑related entities (Job, JobInstance, Step).

JobLauncher – a simple interface used to launch a Job with a given set of JobParameters.

Job – encapsulates the entire batch process.

Step – represents an independent sequential phase of a job.

3.2 Core Interfaces

ItemReader

: abstraction that reads a chunk of items for a Step. ItemProcessor: abstraction that performs business processing on an item. ItemWriter: abstraction that writes a chunk of items for a Step.

The typical flow is Input → Data Processing → Output, where a Job contains multiple Step s, each usually composed of an ItemReader, ItemProcessor, and ItemWriter.

4. Practical Implementation

4.0 Adding Spring Batch

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.2.5.RELEASE</version>
</parent>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
</dependencies>

MySQL schema can be found inside the Spring Batch JAR at org/springframework/batch/core/schema-mysql.sql.

@SpringBootApplication
@EnableBatchProcessing
public class SpringBatchStartApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBatchStartApplication.class, args);
    }
}

4.1 Sample Job Definition

@Component
public class FirstJobDemo {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job firstJob() {
        return jobBuilderFactory.get("firstJob")
                .start(step())
                .build();
    }

    private Step step() {
        return stepBuilderFactory.get("step")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("Executing step...");
                    return RepeatStatus.FINISHED;
                }).build();
    }
}

4.2 Flow Control

A. Multi‑step Job

@Bean
public Job multiStepJob() {
    return jobBuilderFactory.get("multiStepJob")
            .start(step1())
            .on(ExitStatus.COMPLETED.getExitCode()).to(step2())
            .from(step2()).on(ExitStatus.COMPLETED.getExitCode()).to(step3())
            .from(step3()).end()
            .build();
}

private Step step1() { /* tasklet implementation */ }
private Step step2() { /* tasklet implementation */ }
private Step step3() { /* tasklet implementation */ }

B. Parallel Execution

@Bean
public Job splitJob() {
    return jobBuilderFactory.get("splitJob")
            .start(flow1())
            .split(new SimpleAsyncTaskExecutor())
            .add(flow2())
            .end()
            .build();
}

private Flow flow1() { return new FlowBuilder<>("flow1").start(step1()).next(step2()).build(); }
private Flow flow2() { return new FlowBuilder<>("flow2").start(step3()).build(); }

C. Decision Flow

@Component
public class MyDecider implements JobExecutionDecider {
    @Override
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        DayOfWeek day = LocalDate.now().getDayOfWeek();
        if (day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY) {
            return new FlowExecutionStatus("weekend");
        } else {
            return new FlowExecutionStatus("workingDay");
        }
    }
}

@Bean
public Job deciderJob() {
    return jobBuilderFactory.get("deciderJob")
            .start(step1())
            .next(myDecider)
            .from(myDecider).on("weekend").to(step2())
            .from(myDecider).on("workingDay").to(step3())
            .from(step3()).on("*").to(step4())
            .end()
            .build();
}

D. Nested Jobs

@Component
public class NestedJobDemo {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;
    @Autowired private JobLauncher jobLauncher;
    @Autowired private JobRepository jobRepository;
    @Autowired private PlatformTransactionManager platformTransactionManager;

    @Bean
    public Job parentJob() {
        return jobBuilderFactory.get("parentJob")
                .start(childJobOneStep())
                .next(childJobTwoStep())
                .build();
    }

    private Step childJobOneStep() {
        return new JobStepBuilder(new StepBuilder("childJobOneStep"))
                .job(childJobOne())
                .launcher(jobLauncher)
                .repository(jobRepository)
                .transactionManager(platformTransactionManager)
                .build();
    }

    private Step childJobTwoStep() { /* similar to above */ }

    private Job childJobOne() { /* simple step that prints a message */ }
    private Job childJobTwo() { /* simple step that prints a message */ }
}

4.3 Reading Data

Define a POJO model:

@Data
public class TestData {
    private int id;
    private String field1;
    private String field2;
    private String field3;
}

FlatFileItemReader configuration (CSV example):

@Bean
public Job fileItemReaderJob() {
    return jobBuilderFactory.get("fileItemReaderJob")
            .start(step())
            .build();
}

private Step step() {
    return stepBuilderFactory.get("step")
            .<TestData, TestData>chunk(2)
            .reader(fileItemReader())
            .writer(list -> list.forEach(System.out::println))
            .build();
}

private ItemReader<TestData> fileItemReader() {
    FlatFileItemReader<TestData> reader = new FlatFileItemReader<>();
    reader.setResource(new ClassPathResource("reader/file"));
    reader.setLinesToSkip(1);
    DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
    tokenizer.setNames("id", "field1", "field2", "field3");
    DefaultLineMapper<TestData> mapper = new DefaultLineMapper<>();
    mapper.setLineTokenizer(tokenizer);
    mapper.setFieldSetMapper(fieldSet -> {
        TestData data = new TestData();
        data.setId(fieldSet.readInt("id"));
        data.setField1(fieldSet.readString("field1"));
        data.setField2(fieldSet.readString("field2"));
        data.setField3(fieldSet.readString("field3"));
        return data;
    });
    reader.setLineMapper(mapper);
    return reader;
}

4.4 Writing Data

@Component
public class FileItemWriterDemo {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;
    @Resource(name = "writerSimpleReader")
    private ListItemReader<TestData> writerSimpleReader;

    @Bean
    public Job fileItemWriterJob() throws Exception {
        return jobBuilderFactory.get("fileItemWriterJob")
                .start(step())
                .build();
    }

    private Step step() throws Exception {
        return stepBuilderFactory.get("step")
                .<TestData, TestData>chunk(2)
                .reader(writerSimpleReader)
                .writer(fileItemWriter())
                .build();
    }

    private FlatFileItemWriter<TestData> fileItemWriter() throws Exception {
        FlatFileItemWriter<TestData> writer = new FlatFileItemWriter<>();
        FileSystemResource file = new FileSystemResource("D:/code/spring-batch-demo/src/main/resources/writer/writer-file");
        Path path = Paths.get(file.getPath());
        if (!Files.exists(path)) {
            Files.createFile(path);
        }
        writer.setResource(file);
        writer.setLineAggregator(item -> {
            try {
                return new ObjectMapper().writeValueAsString(item);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return "";
            }
        });
        writer.afterPropertiesSet();
        return writer;
    }
}

4.5 Processing Data (Validation)

@Component
public class ValidatingItemProcessorDemo {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;
    @Resource(name = "processorSimpleReader")
    private ListItemReader<TestData> processorSimpleReader;

    @Bean
    public Job validatingItemProcessorJob() throws Exception {
        return jobBuilderFactory.get("validatingItemProcessorJob")
                .start(step())
                .build();
    }

    private Step step() throws Exception {
        return stepBuilderFactory.get("step")
                .<TestData, TestData>chunk(2)
                .reader(processorSimpleReader)
                .processor(beanValidatingItemProcessor())
                .writer(list -> list.forEach(System.out::println))
                .build();
    }

    private BeanValidatingItemProcessor<TestData> beanValidatingItemProcessor() throws Exception {
        BeanValidatingItemProcessor<TestData> processor = new BeanValidatingItemProcessor<>();
        processor.afterPropertiesSet();
        return processor;
    }
}

4.6 Job Scheduling via REST

@RestController
@RequestMapping("job")
public class JobController {
    @Autowired private Job job;
    @Autowired private JobLauncher jobLauncher;

    @GetMapping("launcher/{message}")
    public String launch(@PathVariable String message) throws Exception {
        JobParameters parameters = new JobParametersBuilder()
                .addString("message", message)
                .toJobParameters();
        jobLauncher.run(job, parameters);
        return "success";
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBatch ProcessingSpring BootJob SchedulingSpring Batch
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.