Skip to content
Development
Skill

/spring-batch

Use when building batch jobs, ETL pipelines, scheduled imports/exports, or any chunk-oriented bulk processing with Spring Batch. Covers the Spring Batch 5 / Boot 3 builder API, restartability and idempotent job parameters, reader/writer thread-safety, fault tolerance, and chunk

From plugin
spring-boot-skills
26533 skills
Install
$ npx -y skills add rrezartprebreza/spring-boot-skills --skill spring-batch --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/spring-batch

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when building batch jobs, ETL pipelines, scheduled imports/exports, or any chunk-oriented bulk processing with Spring Batch. Covers the Spring Batch 5 / Boot 3 builder API, restartability and idempotent job parameters, reader/writer thread-safety, fault tolerance, and chunk

SKILL.md

spring-batch.SKILL.md
name: spring-batch
description: >
  Use when building batch jobs, ETL pipelines, scheduled imports/exports, or any chunk-oriented
  bulk processing with Spring Batch. Covers the Spring Batch 5 / Boot 3 builder API, restartability
  and idempotent job parameters, reader/writer thread-safety, fault tolerance, and chunk transaction
  boundaries.

Spring Batch

Spring Boot 3.x ships **Spring Batch 5**. The API changed significantly from 4.x — most online examples are wrong. The two rules that break the most agent-generated code:

1. **Do NOT add `@EnableBatchProcessing`.** Boot auto-configures the `JobRepository`, `JobLauncher`, and transaction manager. Adding `@EnableBatchProcessing` **disables** that auto-configuration and you lose all the wired beans. 2. **`JobBuilderFactory` and `StepBuilderFactory` are gone.** Build jobs and steps with `new JobBuilder(name, jobRepository)` / `new StepBuilder(name, jobRepository)`, injecting the `JobRepository` and `PlatformTransactionManager` Boot already created.

Job & Step (Spring Batch 5 API)

@Configuration
@RequiredArgsConstructor
public class OrderExportJobConfig {

    @Bean
    public Job orderExportJob(JobRepository jobRepository, Step exportStep) {
        return new JobBuilder("orderExportJob", jobRepository)
            .incrementer(new RunIdIncrementer()) // lets the same job be re-run; see "Idempotency"
            .start(exportStep)
            .build();
    }

    @Bean
    public Step exportStep(JobRepository jobRepository,
                           PlatformTransactionManager txManager, // Boot's, injected — do NOT new one up
                           ItemReader<Order> reader,
                           ItemProcessor<Order, OrderRow> processor,
                           ItemWriter<OrderRow> writer) {
        return new StepBuilder("exportStep", jobRepository)
            .<Order, OrderRow>chunk(500, txManager) // chunk size is the commit interval — and a TX boundary
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .faultTolerant()
            .skip(FlatFileParseException.class)
            .skipLimit(50)
            .build();
    }
}

`chunk(500, txManager)` means: read 500 items, process each, hand the list of 500 to the writer, **commit one transaction**, repeat. The chunk is the unit of restart and the unit of rollback.

Idempotency & Restartability — the #1 operational gotcha

A `JobInstance` is identified by its **identifying** `JobParameters`. Launch the same job with the same identifying parameters twice and you get:

JobInstanceAlreadyCompleteException: A job instance already exists and is complete

This is by design — Batch refuses to re-run completed work. Two ways to handle it:

// Option A — RunIdIncrementer on the job (above) + JobLauncherApplicationRunner bumps run.id each launch.
// Option B — add a unique identifying parameter yourself when launching:
JobParameters params = new JobParametersBuilder()
    .addString("status", "COMPLETED")              // identifying — part of the instance key
    .addLong("run.id", System.currentTimeMillis()) // identifying & unique — makes each run a new instance
    .toJobParameters();

Mark a parameter **non-identifying** with the `false` flag when it's metadata that shouldn't change the instance identity (e.g. a request id you log but don't key on):

.addString("requestId", requestId, false) // non-identifying — excluded from the instance key

A **failed** job, by contrast, is *resumed* when relaunched with the **same** parameters — it skips completed steps and restarts the failed step from the last committed chunk. That is the point of the metadata tables. Don't defeat it by always passing a unique parameter if you want resume-on-failure.

ItemReader — sort key and thread-safety

@Bean
@StepScope // required: late-binds jobParameters at step execution, not context startup
public JpaPagingItemReader<Order> orderReader(
        EntityManagerFactory emf,
        @Value("#{jobParameters['status']}") String status) {
    return new JpaPagingItemReaderBuilder<Order>()
        .name("orderReader")
        .entityManagerFactory(emf)
        .queryString("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.id") // ORDER BY is MANDATORY
        .parameterValues(Map.of("status", OrderStatus.valueOf(status)))
        .pageSize(500) // keep pageSize == chunk size
        .build();
}
  • **Paging readers require a deterministic `ORDER BY`** on a unique column. Without it the DB returns

rows in arbitrary order across pages → rows get **skipped or processed twice**. This is silent data corruption, not an error.

  • **`JdbcCursorItemReader` is NOT thread-safe.** `JdbcPagingItemReader` / `JpaPagingItemReader` are

safe for multi-threaded steps. For a non-thread-safe reader in a multi-threaded step, wrap it in `SynchronizedItemStreamReader`.

  • **Don't mutate the column you page on inside the same job.** If the writer flips `status` from

`PENDING` to `DONE` while the reader pages `WHERE status = 'PENDING' ORDER BY id`, the result set shifts under you and pages are missed. Read into a stable snapshot, page by immutable `id`, or use a cursor reader.

ItemProcessor — returning null filters

@Component
public class OrderProcessor implements ItemProcessor<Order, OrderRow> {
    @Override
    public OrderRow process(Order order) {
        if (order.getTotal().isZero()) {
            return null; // ⚠️ null = FILTER this item; it is NOT written and NOT an error
        }
        return OrderRow.from(order);
    }
}

Returning `null` silently drops the item from the chunk. That's a feature (filtering) but a footgun if you returned `null` by accident expecting it to pass through.

ItemWriter — Spring Batch 5 signature change

In 5.x the writer receives a `Chunk<? extends T>`, **not** `List<? extends T>`:

@Override
public void wr
Read more
Ships withspring-boot-skills

Production-grade Claude Code and Codex skills for Spring Boot developers

Get the whole plugin

Other skills on spring-boot-skills.