This episode covers scheduled jobs and large-scale data processing: Spring Batch with jobs, steps, readers, processors, and writers; scheduled tasks with @Scheduled and cron expressions; and asynchronous execution with a task executor.

Not all work happens because of a user request. Cleaning up old data, sending emails, processing millions of file rows, or updating reports are jobs that run in the background. Episode 11 covers batch processing and scheduling.
You'll learn to use Spring Batch for processing large volumes of data with jobs and steps, schedule tasks with @Scheduled, and execute tasks asynchronously so they don't block the main thread.
Spring provides simple scheduling. Enable it with @EnableScheduling on the main class, then mark a method with @Scheduled:
@Component
public class ReportTask {
private static final Logger log =
LoggerFactory.getLogger(ReportTask.class);
@Scheduled(fixedRate = 60000)
public void generateDailyReport() {
log.info("Menjalankan pembuatan laporan");
// business logic
}
}fixedRate = 60000 runs the method every 60 seconds, measured from the start of the previous execution. There's also fixedDelay, which is measured from the end of the execution, and initialDelay for the first delay.
For more complex schedules, use cron expressions — six or seven segments for seconds, minutes, hours, day, month, and day of week. The expression 0 0 0 * * * means every day at 00:00:00. Another example: 0 0 2 * * MON-FRI for 02:00 on weekdays. Understand the cron segment order because it's a frequent source of configuration mistakes.
For processing large volumes of data — for example reading millions of file rows and storing them in a database — use Spring Batch. Its structure consists of a Job containing one or more Steps. Each Step uses an ItemReader to read, an ItemProcessor to process, and an ItemWriter to write.
Add the dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>An example job that reads data from a CSV file and stores it in a database:
@Configuration
public class BatchConfig {
@Bean
public Job importItemJob(JobRepository repo, Step step) {
return new JobBuilder("importItemJob", repo)
.start(step)
.build();
}
@Bean
public Step importStep(JobRepository repo,
PlatformTransactionManager tx,
ItemReader<Item> reader,
ItemWriter<Item> writer) {
return new StepBuilder("importStep", repo)
.<Item, Item>chunk(100, tx)
.reader(reader)
.writer(writer)
.build();
}
}The step above processes data in chunks of 100 — reads 100 items, then writes them in a single transaction. The chunk size determines the balance between memory usage and the number of transactions.
ItemReader and ItemWriter can be built-in implementations or custom ones. The most common example uses FlatFileItemReader for CSV files:
@Bean
public FlatFileItemReader<Item> reader() {
return new FlatFileItemReaderBuilder<Item>()
.name("itemReader")
.resource(new ClassPathResource("data/items.csv"))
.delimited()
.names("name", "price")
.targetType(Item.class)
.build();
}With this pattern, Spring Batch handles chunking, transactions, and automatic restarts — if a job fails midway, the job can resume from the last successful point.
One of Spring Batch's advantages is restartability: execution status is stored in Spring Batch's metadata tables, so a failed job can be re-run without repeating chunks that already succeeded. This is important for long-running processing.
Run the same job with different parameters — for example a report date — via job parameters:
./mvnw spring-boot:run -Dspring-boot.run.arguments=--date=2026-08-10The --date parameter can be accessed in the reader and processor via @StepScope and @Value, allowing the same job to be used for different inputs.
Time-consuming tasks must not block user requests. Enable @EnableAsync, then mark a method with @Async:
@SpringBootApplication
@EnableAsync
public class BelajarSpringBootApplication {
// ...
}@Service
public class NotificationService {
@Async
public void sendWelcomeEmail(String email) {
// sending the email doesn't block the caller
}
}An @Async method executes on a separate thread pool and returns control to the caller immediately. Make sure the call happens between beans — self-invocation won't trigger async behavior.
Set the thread pool size for better control:
spring:
task:
execution:
pool:
core-size: 4
max-size: 16
queue-capacity: 100The configuration above limits the thread pool to between 4 and 16 threads with a queue of 100 tasks. The pool size must be adjusted to server capacity and the nature of the work — too small causes queues, too large strains memory.
Episode 11 equipped you with scheduled processing: scheduled tasks with @Scheduled and cron expressions, Spring Batch for processing large data volumes with jobs, steps, readers, processors, and writers, and asynchronous execution with @Async and a task executor.
Key takeaways:
@EnableScheduling, then use @Scheduled.@Async executes tasks on a thread pool without blocking the caller.In the next episode, episode 12, we'll discuss basic security and authentication — Spring Security and the filter chain, configuring the authentication manager and user details, form login and basic auth, and CSRF, CORS, and security header protection.