Scheduling and Async
@Scheduled, @Async, thread pool management, cron expressions
Scheduling and Async
Scheduling and async execution are useful only when you understand not just what runs, but when it runs, on which threads, and with what failure semantics.
1. Definíció / Definition
Mi ez? / What is it?
Spring scheduling provides time-based execution of application tasks, while async execution offloads work from the caller thread to an executor. They are related but distinct concerns: scheduling answers when work begins, async answers how it is executed.
Miért létezik? / Why does it exist?
Because many useful workloads do not belong on the synchronous request path: report generation, cleanup jobs, cache refreshes, email sending, retry workflows, or periodic integration polling.
Hol helyezkedik el? / Where does it fit?
These features sit at the boundary of application services and infrastructure. They interact with thread pools, transactions, monitoring, graceful shutdown, and cluster topology.
2. Alapfogalmak / Core Concepts
2.1 `@Scheduled`
@Scheduled declares methods that run on a time-based trigger. It requires @EnableScheduling.
Common modes:
- fixedRate: schedule relative to start time.
- fixedDelay: schedule relative to completion time.
- cron: schedule by calendar expression.
| Mode | Measures | Best fit |
|---|---|---|
| fixedRate | start-to-start interval | heartbeat, polling |
| fixedDelay | end-to-start interval | cleanup loops, variable jobs |
| cron | calendar rule | reports, cutoff jobs |
2.2 Spring cron syntax
Spring cron expressions include a seconds field, which often surprises developers coming from Unix cron.
Cron field order: seconds minutes hours day-of-month month day-of-week
Example: 0 0 2 * * * means 02:00:00 every day.
That means 02:00:00 every day. Copy-pasting a five-field cron into Spring is a classic configuration error.
2.3 `@Async`
@Async runs a method through a Spring proxy on another thread. Common return types include:
voidFuture<T>CompletableFuture<T>
Production systems should almost always provide an explicit executor instead of relying on defaults.
2.4 Scheduling vs execution
A scheduler triggers work; an executor runs work.
Key difference:
TaskSchedulerdecides when work should start.TaskExecutordecides which thread performs the work.
Relevant Spring types:
- ThreadPoolTaskScheduler for time-based triggers.
- ThreadPoolTaskExecutor for async task execution.
Confusing the two leads to poor tuning, starvation, or unbounded queues in the wrong place.
2.5 Async exception handling
Errors in @Async void methods do not naturally bubble back to the caller. That means you need one of two patterns:
AsyncUncaughtExceptionHandlerfor fire-and-forget async methods;CompletableFutureand explicit exception composition for value-returning workflows.
2.6 One instance versus many instances
@Scheduled runs in every application instance. In a three-pod deployment, the same scheduled method runs three times unless you introduce coordination.
Common solutions:
- ShedLock for simple distributed locking backed by a shared store.
- Quartz for richer persistent scheduling.
3. Gyakorlati használat / Practical Usage
A standard use case is periodic cleanup: deleting expired sessions, rotating stale export files, or syncing a cache from a reference source. These are often good fits for fixedDelay, because the next run should wait until the previous execution completes.
Daily reports are a more calendar-driven use case. They belong to cron scheduling, and in clustered production systems they usually need a distributed lock. Otherwise each node generates the same report independently, which is both wasteful and potentially harmful.
Async execution is a common fit for secondary user workflows such as welcome emails, audit publication, or post-processing that should not block an HTTP request. Still, async is not a magic performance upgrade; it merely shifts work to another thread pool. If the downstream system is slow, the cost remains real somewhere in the node.
In larger systems, separate executors become essential. Bulk report generation, outbound email, and lightweight cache refreshes should not contend in a single shared pool. Thread pools express operational policy, not just implementation detail.
4. Kód példák / Code Examples
4.1 Enabling scheduling and async execution
@Configuration
@EnableScheduling
@EnableAsync
public class TaskConfiguration implements AsyncConfigurer {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.initialize();
return scheduler;
}
@Bean(name = "notificationExecutor")
public ThreadPoolTaskExecutor notificationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("notify-");
executor.initialize();
return executor;
}
@Override
public Executor getAsyncExecutor() {
return notificationExecutor();
}
}
4.2 Scheduled jobs
@Service
public class MaintenanceJobs {
@Scheduled(cron = "0 0 1 * * *")
public void generateDailySummary() {
// run once a day at 01:00
}
@Scheduled(fixedDelay = 30000)
public void refreshReferenceData() {
// run again 30 seconds after completion
}
}
4.3 Async email sending
@Service
public class EmailService {
@Async("notificationExecutor")
public CompletableFuture<Void> sendWelcomeEmail(String email) {
callProvider(email);
return CompletableFuture.completedFuture(null);
}
private void callProvider(String email) {
// invoke remote provider
}
}
4.4 Async exception handling
@Configuration
public class AsyncErrorHandlingConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, params) ->
LoggerFactory.getLogger(method.getDeclaringClass())
.error("Async failure in {} with params {}", method.getName(), Arrays.toString(params), throwable);
}
}
5. Trade-offok / Trade-offs
Benefits
- simple scheduling for many application-local jobs;
- cleaner request latency by moving secondary work off the caller thread;
- readable declarative model with annotations;
- enough power for many small and medium production use cases.
Costs
- proxy-based behavior can surprise developers;
- poor thread-pool sizing causes contention or hidden backlog;
- clustered execution can duplicate jobs;
- async execution does not replace durable queues or workflow orchestration.
Use it when the problem is local to one service and does not require strong durability or complex orchestration.
Use something else when you need persistent scheduling, strict once-only semantics, large batch orchestration, or retryable durable delivery.
6. Gyakori hibák / Common Mistakes
6.1 Self-invocation bypassing proxies
Calling an @Async method from another method in the same bean often bypasses the Spring proxy. The result is synchronous execution when developers expected async behavior.
6.2 Relying on default executors
Defaults are convenient for demos, not operational policy. Without explicit pools, teams lose control over queueing, thread naming, sizing, and shutdown behavior.
6.3 Using fixedRate for slow jobs
If execution time exceeds the interval, jobs overlap. That can create duplicate work, lock contention, and resource exhaustion.
6.4 Misunderstanding transactions across async boundaries
@Transactional context does not simply “continue in the background” when you hop threads. Async execution creates a new call path with different transactional behavior.
6.5 Forgetting cluster coordination
A scheduled billing or settlement job running on every pod is not a minor defect; it can become a business incident. Design cluster behavior explicitly.
7. Senior szintű meglátások / Senior-level Insights
The senior question is not “can Spring schedule this?” but “what execution semantics does the business require?” Is duplicate execution acceptable? Is the task idempotent? Do we need at-most-once, at-least-once, or merely best effort? Those answers determine whether @Scheduled is sufficient or whether you need distributed locks, messaging, or an external scheduler.
Treat executors as policy boundaries. A single large pool may look simple in configuration, but it collapses operational priorities. Report generation should not starve transactional notifications; low-value cache refresh should not consume capacity needed by high-value workflows.
Use async sparingly for CPU-bound work. It is excellent for decoupling request latency from slower I/O, but it does not create free compute. If all async tasks become CPU-heavy, you merely move saturation from request threads to worker threads.
Finally, background jobs need observability equal to API endpoints. Count executions, measure duration, track failures, and name threads predictably. The system is easier to operate when invisible work becomes visible telemetry.
8. Szószedet / Glossary
@Scheduled: Spring annotation for time-triggered execution.@Async: Spring annotation for asynchronous method execution.- fixedRate: fixed interval between starts.
- fixedDelay: fixed interval after completion.
- cron: calendar-based trigger expression.
- TaskScheduler: scheduling infrastructure.
- TaskExecutor: async execution infrastructure.
- CompletableFuture: composable asynchronous result container.
- ShedLock: distributed lock helper for scheduled jobs.
- Quartz: advanced persistent scheduler.
9. Gyorsreferencia / Cheatsheet
| Topic | Good default | Watch out for |
|---|---|---|
@Scheduled |
simple periodic jobs | each pod runs it |
| fixedRate | heartbeat and polling | overlapping runs |
| fixedDelay | variable-duration work | schedule drift |
| cron | calendar events | Spring includes seconds |
@Async |
I/O-heavy secondary workflows | define your own executor |
CompletableFuture |
error and result composition | exception handling discipline |
| TaskScheduler | timing control | separate from execution pools |
| TaskExecutor | async worker pool | queue and thread sizing |
| ShedLock | clustered single execution | shared lock store required |
🎮 Games
8 questions