Scheduling, Async & Spring Batch
01 What is @Async and when should it not be used?
Solution and explanation
Solution:
@Async executes work using a Spring-managed executor instead of blocking the calling thread.
Example:
A user applies for a job.
The main operation:
- Validate candidate.
- Validate job.
- Insert application.
- Commit transaction.
Sending an email can happen separately.
Without asynchronous execution:
Application request
+-- Save application
+-- Send email
+-- Send notification
Response
The user waits for email processing.
With asynchronous execution:
Application request
+-- Save application
Response
Background worker:
+-- Send email +-- Send notification
However, @Async is local to the application process.
If the process crashes, the in-memory task may be lost.
For durable cross-service business events, Kafka or another durable messaging system is generally more appropriate.
Also remember that @Async works through Spring proxies. Calling an @Async method through self-invocation in the same class does not activate asynchronous interception.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("mailExecutor")
public Executor mailExecutor() {
ThreadPoolTaskExecutor executor =
new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("mail-");
executor.initialize();
return executor;
}
}
@Async("mailExecutor")
public CompletableFuture<Void> sendApplicationEmail(
Long applicationId) {
emailService.send(applicationId);
return CompletableFuture.completedFuture(null);
}
Configure a bounded executor and process email notifications asynchronously.
Quick check
Which statement is correct?
02 What is Spring Batch?
Solution and explanation
- Solution:
- Suppose a university has 2,000 employees.
- At the end of every month, salary must be calculated.
- Another company may have:
- 100,000 employees.
- Processing everything as one huge operation can create memory, transaction and recovery problems.
- Spring Batch breaks large processing into controlled steps.
- Example:
Read employee
|
Calculate salary
|
Validate salary
|
Write payroll item
|
Commit chunk
- Suppose chunk size = 200.
- Employees:
- 1-200
- 201-400
- 401-600
- ...
- If employees 1-200 complete successfully and processing employee 401-600 fails, the job can restart according to its configured restart/retry strategy instead of blindly recalculating everything.
- Typical structure:
- Job
- |
- Step
- |
- ItemReader
- |
- ItemProcessor
- |
- ItemWriter
- For payroll:
- Reader:
- employees
- Processor:
- basic + HRA + allowance - deduction
- Writer:
- payroll_items
@Bean
public Step payrollStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder(
"payrollStep",
jobRepository
)
.<Employee, PayrollItem>chunk(
200,
transactionManager
)
.reader(employeeReader())
.processor(employee ->
payrollCalculator.calculate(employee)
)
.writer(payrollWriter())
.build();
}
Process 2,000 employee salaries in chunks of 200 and make the job restartable after a failure.
Quick check
Which statement is correct?
03 How do you design a 10,000-employee salary batch?
Solution and explanation
- Solution:
- Suppose Infosys-like corporate payroll contains:
- 10,000 employees
- Average net salary:
- Rs 75,000
- Total:
- 10,000 × 75,000
- = Rs 750,000,000
- = Rs 75 crore
- The system should not treat this as one giant payment row.
Create:
- salary_batches
- id
- company_id
- batch_reference
- salary_month
- employee_count
- total_amount
- status
- salary_batch_items
- id
- salary_batch_id
- employee_id
- beneficiary_account
- amount
- status
- payment_reference
- failure_code
- Lifecycle:
- DRAFT
- |
- VALIDATED
- |
- APPROVED
- |
- SUBMITTED
- |
- PROCESSING
- |
- COMPLETED
- But processing can have mixed results.
- Example:
- 9970 SUCCESS
- 20 FAILED
- 10 PENDING
- The batch cannot be declared fully successful merely because most employees succeeded.
- The system must retain individual payment outcomes.
- The final batch state can be:
- PARTIALLY_COMPLETED
- while the 10 pending items remain unresolved.
- An external bank timeout should not automatically mean FAILED.
- It may mean UNKNOWN/PENDING until the bank confirms the result.
public enum SalaryBatchStatus {
DRAFT,
VALIDATED,
APPROVED,
SUBMITTED,
PROCESSING,
COMPLETED,
PARTIALLY_COMPLETED,
FAILED,
CANCELLED
}
public enum SalaryItemStatus {
PENDING,
PROCESSING,
SUCCESS,
FAILED,
UNKNOWN
}
public record SalaryBatchResult(
int total,
int success,
int failed,
int pending,
int unknown
) {}
Process 10,000 salary payments individually while maintaining one batch-level lifecycle.
Quick check
Which statement is correct?
