Boost Spring Batch Performance: Async & Virtual Threads in Spring Boot 3
This article demonstrates how to accelerate Spring Batch jobs in Spring Boot 3 by comparing a default synchronous job with asynchronous processing using ThreadPoolTaskExecutor and then with virtual threads, providing code examples, configuration steps, and performance results that show up to a 90‑fold speedup.
Environment
Spring Boot 3.4.2
1. Introduction
Spring Batch is a batch‑processing framework designed for large data sets. It offers high scalability, robust error handling, and transaction management, making it suitable for tasks such as periodic report generation and massive data migration.
2. Practical Cases
2.1 Default Synchronous Job
Define ItemReader, ItemProcessor, and ItemWriter components and configure a synchronous job. The job runs automatically on startup and takes about 54 seconds for the sample data set.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency> spring:
batch:
jdbc:
initialize-schema: always @Component
public class NumberItemReader implements ItemReader<Integer> {
private static final Integer UPPER_BOUND = 500;
private int currentIndex = 0;
public Integer read() {
return (currentIndex < UPPER_BOUND) ? currentIndex++ : null;
}
} @Component
public class SyncItemProcessor implements ItemProcessor<Integer, Integer> {
private static final Logger LOGGER = LoggerFactory.getLogger(SyncItemProcessor.class);
@Override
public Integer process(@NonNull Integer item) throws Exception {
LOGGER.info("Processing item {}", item);
TimeUnit.MILLISECONDS.sleep(100);
return item;
}
} @Component
public class LogItemWriter implements ItemWriter<Integer> {
private static final Logger LOGGER = LoggerFactory.getLogger(LogItemWriter.class);
@Override
public void write(Chunk<? extends Integer> chunk) {
for (var item : chunk) {
LOGGER.info("Writing item {}", item);
}
}
} @Configuration
public class BatchConfiguration {
public static final String SYNC_JOB_NAME = "syncJob";
@Bean
Step syncStep(JobRepository jobRepository, PlatformTransactionManager transactionManager,
NumberItemReader numberItemReader, SyncItemProcessor syncItemProcessor,
LogItemWriter logItemWriter) {
return new StepBuilder("sync-step", jobRepository)
.<Integer, Integer>chunk(100, transactionManager)
.reader(numberItemReader)
.processor(syncItemProcessor)
.writer(logItemWriter)
.build();
}
@Bean(SYNC_JOB_NAME)
Job syncJob(JobRepository jobRepository, Step syncStep) {
return new JobBuilder(SYNC_JOB_NAME, jobRepository)
.incrementer(new RunIdIncrementer())
.start(syncStep)
.build();
}
}Result: total execution time ≈ 54 s.
2.2 Asynchronous Job Configuration
Add the spring-batch-integration dependency and configure an AsyncItemProcessor and AsyncItemWriter backed by a ThreadPoolTaskExecutor with 5‑10 threads.
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-integration</artifactId>
</dependency> @Configuration
public class AsyncBatchConfiguration {
public static final String STEP_NAME = "async-step";
public static final String ASYNC_JOB_NAME = "asyncJob";
@Bean
AsyncItemProcessor<Integer, Integer> asyncItemProcessor(SyncItemProcessor delegate,
TaskExecutor executor) {
var processor = new AsyncItemProcessor<Integer, Integer>();
processor.setDelegate(delegate);
processor.setTaskExecutor(executor);
return processor;
}
@Bean
AsyncItemWriter<Integer> asyncWriter(LogItemWriter delegate) {
var writer = new AsyncItemWriter<Integer>();
writer.setDelegate(delegate);
return writer;
}
@Bean
TaskExecutor threadPoolTaskExecutor() {
var executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("platform-thread-");
return executor;
}
@Bean
Step asyncStep(JobRepository jobRepository, PlatformTransactionManager transactionManager,
NumberItemReader numberItemReader, AsyncItemProcessor<Integer, Integer> asyncProcessor,
AsyncItemWriter<Integer> asyncWriter) {
return new StepBuilder(STEP_NAME, jobRepository)
.<Integer, Future<Integer>>chunk(100, transactionManager)
.reader(numberItemReader)
.processor(asyncProcessor)
.writer(asyncWriter)
.build();
}
@Bean(ASYNC_JOB_NAME)
Job asyncJob(JobRepository jobRepository, Step asyncStep) {
return new JobBuilder(ASYNC_JOB_NAME, jobRepository)
.incrementer(new RunIdIncrementer())
.start(asyncStep)
.build();
}
}Result: total execution time ≈ 11 s (about 5× faster than the synchronous version).
2.3 Virtual Thread Processing
Replace the thread‑pool executor with a VirtualThreadTaskExecutor to leverage Java virtual threads.
@Bean
TaskExecutor threadPoolTaskExecutor() {
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("virtual-thread-");
return executor;
}Result: total execution time ≈ 0.6 s, an 18× improvement over the async thread‑pool version and roughly 90× faster than the original synchronous job.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
