Docs LogoDocs

Async Processing, Scheduling & Messaging

@Async, @Scheduled, and when to move to a real message queue.

Async Processing, Scheduling & Messaging

@Async — Fire-and-Forget or Non-Blocking Work

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}
@Service
public class StatementParsingService {

    @Async
    public CompletableFuture<ParsedStatement> parseInBackground(String statementText) {
        ParsedStatement result = mlPipeline.parse(statementText);
        return CompletableFuture.completedFuture(result);
    }
}

Good for: sending emails, running an ML parsing pipeline after upload, generating a report — anything the caller doesn't need to block on.

Caveat: @Async only works when called from a different Spring bean (self-invocation bypasses the proxy) and silently swallows exceptions unless you configure an AsyncUncaughtExceptionHandler.

@Scheduled — Recurring Jobs

@Component
public class RecurringJobs {

    @Scheduled(cron = "0 0 2 * * *") // 2 AM daily
    public void reconcileDailyTransactions() { ... }

    @Scheduled(fixedRate = 300_000) // every 5 minutes
    public void refreshExchangeRates() { ... }
}

Requires @EnableScheduling on a config class. Note: by default, @Scheduled runs on a single thread — long-running jobs will delay other scheduled tasks unless you configure a dedicated TaskScheduler with a thread pool.

Message Queues: When and Why

Once you need to decouple services, handle spikes in load, or guarantee delivery/retries, move from @Async (in-process, lost on crash) to a real message broker.

ToolTypical Use
RabbitMQTask queues, work distribution, routing by topic/exchange
KafkaHigh-throughput event streaming, event sourcing, log-based processing
Redis StreamsLightweight pub/sub when you already run Redis

Kafka with Spring Boot (sketch)

@Service
public class TransactionEventPublisher {

    private final KafkaTemplate<String, TransactionEvent> kafkaTemplate;

    public void publish(TransactionEvent event) {
        kafkaTemplate.send("transaction-events", event.userId(), event);
    }
}

@KafkaListener(topics = "transaction-events", groupId = "fintrack-analytics")
public void consume(TransactionEvent event) {
    // update analytics/aggregates asynchronously
}

Choosing: @Async vs a Queue

  • @Async — simple, in-process, fine for non-critical background work in a single-instance app. Work is lost if the app crashes mid-task. Good starting point for a solo project.
  • Message queue — needed once you scale to multiple instances, need guaranteed delivery/retry, or want to decouple producer and consumer services entirely.
Last updated on July 15, 2026

On this page