ai-observability
Use when adding Spring AI-specific model observations, token usage, latency, externally configured cost attribution, advisor telemetry, or protected prompt and…
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
$ npx -y skills add rrezartprebreza/spring-boot-skills --skill spring-batch --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/spring-batchContext 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
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 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.
@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.
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 keyA **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.
@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();
}rows in arbitrary order across pages → rows get **skipped or processed twice**. This is silent data corruption, not an error.
safe for multi-threaded steps. For a non-thread-safe reader in a multi-threaded step, wrap it in `SynchronizedItemStreamReader`.
`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.
@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.
In 5.x the writer receives a `Chunk<? extends T>`, **not** `List<? extends T>`:
@Override public void wr
Production-grade Claude Code and Codex skills for Spring Boot developers
Use when adding Spring AI-specific model observations, token usage, latency, externally configured cost attribution, advisor telemetry, or protected prompt and…
Use when versioning Spring MVC or WebFlux APIs in Spring Boot 3 / Spring Framework 6. Covers explicit URL, header, and media-type strategies, compatibility…
Use when introducing or correcting grouped Spring Boot configuration, typed property binding, validation, profiles or secret injection. Do not rewrite…
Use when packaging a Spring Boot 3 application as an OCI image or GraalVM native executable. Covers buildpacks, layered images, JVM containers, AOT hints,…
Use when working with domain models, aggregates, value objects, domain events, or repositories in a DDD-style project. Ensures rich domain model over anemic…
Use when implementing Kafka, RabbitMQ, Pulsar, or JMS producers and consumers in Spring Boot 3. Covers event contracts, idempotency, retries, dead-letter…