Insights into Company
← Spring Boot

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:

  1. Validate candidate.
  2. Validate job.
  3. Insert application.
  4. 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.

Run this example
        @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);
        }
Hands-on challenge

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
Run this example
        @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();
        }
Hands-on challenge

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.
Run this example
        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
        ) {}
Hands-on challenge

Process 10,000 salary payments individually while maintaining one batch-level lifecycle.

Quick check

Which statement is correct?