mcpField
S
spring-batch

spring-batch

github.com/claude-dev-suiteData & databases4652 installsUpdated 4d ago
Repository

About spring-batch

This Spring Batch skill is a practical reference for building Java batch jobs with Spring Batch. It covers job and step configuration, chunk-oriented input and output, parameters, troubleshooting, and operational practices.

Key features

  1. 01Job and step configuration examples
  2. 02Chunk-based read, process, and write
  3. 03File, JDBC, and JPA readers and writers
  4. 04Job parameter usage
  5. 05Troubleshooting and implementation practices

Use cases

  1. 01Build a CSV import and export job
  2. 02Configure chunk processing for large volumes
  3. 03Set up database paging for batch reads
  4. 04Review restart and failure-handling approaches

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill spring-batch

After installing, type/spring-batchin Claude Code to confirm it loaded.

Bundled filesadvanced.md

Details

From the project README

Spring Batch

Full Reference: See advanced.md for skip/retry configuration, partitioning, listeners, testing with JobLauncherTestUtils, composite writers, and async processing.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-boot and topic: batch for comprehensive documentation.

Quick Start

@Configuration
@EnableBatchProcessing
public class BatchConfig {

    @Bean
    public Job importJob(JobRepository jobRepository, Step step1) {
        return new JobBuilder("importJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(step1)
            .build();
    }

    @Bean
    public Step step1(JobRepository jobRepository,
                      PlatformTransactionManager transactionManager,
                      ItemReader<InputData> reader,
                      ItemProcessor<InputData, OutputData> processor,
                      ItemWriter<OutputData> writer) {
        return new StepBuilder("step1", jobRepository)
            .<InputData, OutputData>chunk(100, transactionManager)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }
}

Core Components

Job
@Bean
public Job complexJob(JobRepository jobRepository,
                      Step extractStep, Step transformStep, Step loadStep) {
    return new JobBuilder("etlJob", jobRepository)
        .incrementer(new RunIdIncrementer())
        .validator(jobParametersValidator())
        .listener(jobExecutionListener())
        .start(extractStep)
        .next(transformStep)
        .next(loadStep)
        .build();
}

// Job with decision
@Bean
public Job conditionalJob(JobRepository jobRepository,
                          Step step1, Step step2, Step errorStep,
                          JobExecutionDecider decider) {
    return new JobBuilder("conditionalJob", jobRepository)
        .start(step1)
        .next(decider)
        .on("COMPLETED").to(step2)
        .from(decider).on("FAILED").to(errorStep)
        .end()
        .build();
}
Step - Chunk Processing
@Bean
public Step chunkStep(JobRepository jobRepository,
                      PlatformTransactionManager txManager) {
    return new StepBuilder("chunkStep", jobRepository)
        .<Person, Person>chunk(100, txManager)
        .reader(reader())
        .processor(processor())
        .writer(writer())
        .faultTolerant()
        .skipLimit(10)
        .skip(ValidationException.class)
        .retryLimit(3)
        .retry(TransientException.class)
        .build();
}

// Tasklet Step (for simple operations)
@Bean
public Step taskletStep(JobRepository jobRepository,
                        PlatformTransactionManager txManager) {
    return new StepBuilder("taskletStep", jobRepository)
        .tasklet((contribution, chunkContext) -> {
            cleanupService.cleanup();
            return RepeatStatus.FINISHED;
        }, txManager)
        .build();
}

ItemReader

FlatFileItemReader
@Bean
public FlatFileItemReader<Person> csvReader() {
    return new FlatFileItemReaderBuilder<Person>()
        .name("personReader")
        .resource(new ClassPathResource("data/input.csv"))
        .delimited()
        .delimiter(",")
        .names("firstName", "lastName", "email", "age")
        .linesToSkip(1)  // Skip header
        .fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
            setTargetType(Person.class);
        }})
        .build();
}
JdbcPagingItemReader
@Bean
public JdbcPagingItemReader<Person> pagingReader(DataSource dataSource) {
    Map<String, Order> sortKeys = new HashMap<>();
    sortKeys.put("id", Order.ASCENDING);

    return new JdbcPagingItemReaderBuilder<Person>()
        .name("pagingReader")
        .dataSource(dataSource)
        .selectClause("SELECT id, first_name, last_name, email")
        .fromClause("FROM persons")
        .whereClause("WHERE status = :status")
        .parameterValues(Map.of("status", "ACTIVE"))
        .sortKeys(sortKeys)
        .rowMapper(new BeanPropertyRowMapper<>(Person.class))
        .pageSize(100)
        .build();
}
JpaPagingItemReader
@Bean
public JpaPagingItemReader<Person> jpaReader(EntityManagerFactory emf) {
    return new JpaPagingItemReaderBuilder<Person>()
        .name("jpaReader")
        .entityManagerFactory(emf)
        .queryString("SELECT p FROM Person p WHERE p.status = :status")
        .parameterValues(Map.of("status", Status.ACTIVE))
        .pageSize(100)
        .build();
}

ItemProcessor

@Component
public class PersonProcessor implements ItemProcessor<Person, Person> {

    @Override
    public Person process(Person person) throws Exception {
        // Return null to filter out item
        if (!isValid(person)) {
            return null;
        }

        // Transform
        person.setEmail(person.getEmail().toLowerCase());
        person.setFullName(person.getFirstName() + " " + person.getLastName());
        return person;
    }
}

// Composite processor
@Bean
public CompositeItemProcessor<Person, Person> compositeProcessor() {
    return new CompositeItemProcessorBuilder<Person, Person>()
        .delegates(List.of(
            validationProcessor(),
            transformationProcessor(),
            enrichmentProcessor()
        ))
        .build();
}

ItemWriter

JdbcBatchItemWriter
@Bean
public JdbcBatchItemWriter<Person> jdbcWriter(DataSource dataSource) {
    return new JdbcBatchItemWriterBuilder<Person>()
        .dataSource(dataSource)
        .sql("INSERT INTO persons (first_name, last_name, email) VALUES (:firstName, :lastName, :email)")
        .beanMapped()
        .build();
}
JpaItemWriter
@Bean
public JpaItemWriter<Person> jpaWriter(EntityManagerFactory emf) {
    JpaItemWriter<Person> writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(emf);
    writer.setUsePersist(true);  // false = merge
    return writer;
}
FlatFileItemWriter
@Bean
public FlatFileItemWriter<Person> csvWriter() {
    return new FlatFileItemWriterBuilder<Person>()
        .name("personWriter")
        .resource(new FileSystemResource("output/persons.csv"))
        .delimited()
        .delimiter(",")
        .names("firstName", "lastName", "email")
        .headerCallback(writer -> writer.write("First Name,Last Name,Email"))
        .build();
}

Job Parameters

@Bean
@StepScope
public FlatFileItemReader<Person> parameterizedReader(
        @Value("#{jobParameters['inputFile']}") String inputFile) {
    return new FlatFileItemReaderBuilder<Person>()
        .name("reader")
        .resource(new FileSystemResource(inputFile))
        .delimited()
        .names("firstName", "lastName", "email")
        .targetType(Person.class)
        .build();
}

// Running job with parameters
@Service
@RequiredArgsConstructor
public class JobLauncherService {

    private final JobLauncher jobLauncher;
    private final Job importJob;

    public void runJob(String inputFile, LocalDate date) throws Exception {
        JobParameters params = new JobParametersBuilder()
            .addString("inputFile", inputFile)
            .addLocalDate("date", date)
            .addLong("timestamp", System.currentTimeMillis())
            .toJobParameters();

        JobExecution execution = jobLauncher.run(importJob, params);
        log.info("Job status: {}", execution.getStatus());
    }
}

When NOT to Use This Skill

  • Real-time processing - Use streaming (Kafka Streams, Flink)
  • Simple scheduled tasks - Use spring-scheduling instead
  • Microservices data sync - Consider event-driven with messaging
  • Small data sets - Batch overhead may not be justified

Anti-Patterns

Anti-PatternProblemSolution
Large chunk sizeMemory issues, long transactionsTune chunk size (100-1000)
No skip policySingle error stops jobConfigure skip for expected errors
Stateful ItemProcessorThread safety issuesMake processor stateless
Ignoring job parametersCan't restart failed jobsInclude identifying params
No monitoringSilent failuresConfigure JobExecutionListener
Single-threaded for large dataSlow processingUse partitioning

Quick Troubleshooting

ProblemDiagnosticFix
Job not startingCheck job repositoryVerify database schema
Job fails on restartCheck job parametersAdd RunIdIncrementer
Chunk processing slowCheck commit intervalTune chunk size
Memory issuesMonitor heap usageReduce chunk size, stream data
Skip not workingCheck skip policyConfigure skippable exceptions

Best Practices

  • ✅ Use chunk processing for large volumes
  • ✅ Configure skip/retry for fault tolerance
  • ✅ Use partitioning for parallelism
  • ✅ Implement listeners for monitoring
  • ✅ Test with JobLauncherTestUtils
  • ❌ Don't use chunk size too large
  • ❌ Don't ignore errors silently
  • ❌ Don't forget job parameters for restart

Reference Documentation

Comments

Markdown supported