Skip to main content

Chapter 4: Scheduled and Asynchronous Tasks

Chapter Objectives​

  • Configure scheduled tasks with @Scheduled
  • Master Cron expressions
  • Implement asynchronous methods with @Async
  • Configure thread pools
  • Manage errors and monitoring

1. Basic Configuration​

1.1 Enable Scheduling​

package com.example.scheduling;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SchedulingApplication {

public static void main(String[] args) {
SpringApplication.run(SchedulingApplication.class, args);
}
}

1.2 Thread Pool Configuration​

package com.example.scheduling.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}

@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setErrorHandler(t -> {
// Log errors
System.err.println("Scheduled task error: " + t.getMessage());
});
return scheduler;
}
}

2. @Scheduled - Scheduled Tasks​

2.1 Fixed Rate​

Executes at a fixed interval from the start of the last execution.

@Component
public class ScheduledTasks {

private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

// Every 5 seconds
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("Current time: {}", LocalDateTime.now());
}

// With initial delay of 10 seconds
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void taskWithInitialDelay() {
log.info("Task with initial delay");
}

// Using properties
@Scheduled(fixedRateString = "${task.fixed-rate:5000}")
public void configurableTask() {
log.info("Configurable task");
}
}

2.2 Fixed Delay​

Executes at a fixed interval from the end of the last execution.

@Component
public class ScheduledTasks {

// 3 seconds after the end of the previous execution
@Scheduled(fixedDelay = 3000)
public void processQueue() {
log.info("Processing queue...");
// Simulate processing
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Queue processed");
}
}

2.3 Cron Expressions​

@Component
public class ScheduledTasks {

// Every day at midnight
@Scheduled(cron = "0 0 0 * * *")
public void dailyCleanup() {
log.info("Running daily cleanup");
}

// Every hour, at minute 0
@Scheduled(cron = "0 0 * * * *")
public void hourlyReport() {
log.info("Generating hourly report");
}

// Every Monday at 9 AM
@Scheduled(cron = "0 0 9 * * MON")
public void weeklyReport() {
log.info("Generating weekly report");
}

// The first day of every month at 6 AM
@Scheduled(cron = "0 0 6 1 * *")
public void monthlyReport() {
log.info("Generating monthly report");
}

// Every 15 minutes during business hours
@Scheduled(cron = "0 */15 9-17 * * MON-FRI")
public void businessHoursTask() {
log.info("Business hours task");
}

// With timezone
@Scheduled(cron = "0 0 9 * * *", zone = "America/Montreal")
public void taskWithTimezone() {
log.info("Task at 9 AM Montreal time");
}
}

2.4 Cron Syntax​

┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ │ ┌───────────── day of week (0-7 or SUN-SAT)
│ │ │ │ │ │
* * * * * *
ExpressionDescription
*All values
*/15Every 15
0-23Range of values
1,15List of values
?Not specified (day)
LLast (day of month)
WNearest weekday
#Nth day of the week (2#1 = first Monday)

2.5 Common Cron Examples​

ExpressionDescription
0 0 * * * *Every hour
0 */10 * * * *Every 10 minutes
0 0 8-18 * * *Every hour from 8 AM to 6 PM
0 0 0 * * *Every day at midnight
0 0 0 * * SUNEvery Sunday at midnight
0 0 0 1 * *First day of the month
0 0 0 L * *Last day of the month
0 0 0 ? * 6LLast Friday of the month

3. @Async - Asynchronous Methods​

3.1 Configuration​

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.err.println("Async error in method " + method.getName() + ": " + ex.getMessage());
};
}
}

3.2 Asynchronous Methods​

@Service
public class EmailService {

private static final Logger log = LoggerFactory.getLogger(EmailService.class);

// Fire and forget
@Async
public void sendEmailAsync(String to, String subject, String body) {
log.info("Sending email to {} on thread {}", to, Thread.currentThread().getName());

// Simulate sending
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

log.info("Email sent to {}", to);
}

// With return value
@Async
public CompletableFuture<Boolean> sendEmailWithResult(String to, String subject) {
log.info("Sending email to {}", to);

try {
// Simulate
Thread.sleep(2000);
log.info("Email sent successfully to {}", to);
return CompletableFuture.completedFuture(true);
} catch (Exception e) {
log.error("Failed to send email to {}", to, e);
return CompletableFuture.completedFuture(false);
}
}

// With specific executor
@Async("emailExecutor")
public void sendWithSpecificExecutor(String to) {
// Uses the "emailExecutor" bean
}
}

3.3 Usage​

@RestController
@RequestMapping("/api/orders")
public class OrderController {

private final OrderService orderService;
private final EmailService emailService;

@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// Create order (synchronous)
Order order = orderService.createOrder(request);

// Send email (asynchronous - does not block)
emailService.sendEmailAsync(
request.getEmail(),
"Order Confirmation",
"Your order #" + order.getId() + " has been created"
);

// Returns immediately
return ResponseEntity.ok(order);
}

@PostMapping("/bulk-email")
public ResponseEntity<String> sendBulkEmails(@RequestBody List<String> emails) {
// Send multiple emails in parallel
List<CompletableFuture<Boolean>> futures = emails.stream()
.map(email -> emailService.sendEmailWithResult(email, "Newsletter"))
.collect(Collectors.toList());

// Wait for all results
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long successCount = futures.stream()
.map(CompletableFuture::join)
.filter(Boolean::booleanValue)
.count();
log.info("Sent {} emails successfully", successCount);
});

return ResponseEntity.accepted().body("Emails are being sent");
}
}

4. Common Use Cases​

4.1 Expired Data Cleanup​

@Component
public class CleanupTasks {

private final SessionRepository sessionRepository;
private final TempFileService tempFileService;

// Clean up expired sessions every hour
@Scheduled(cron = "0 0 * * * *")
public void cleanupExpiredSessions() {
log.info("Starting session cleanup");

LocalDateTime expirationThreshold = LocalDateTime.now().minusHours(24);
int deleted = sessionRepository.deleteByLastAccessBefore(expirationThreshold);

log.info("Cleaned up {} expired sessions", deleted);
}

// Clean up temporary files every day at 3 AM
@Scheduled(cron = "0 0 3 * * *")
public void cleanupTempFiles() {
log.info("Starting temp files cleanup");

int deleted = tempFileService.deleteOlderThan(Duration.ofDays(7));

log.info("Deleted {} temp files", deleted);
}
}

4.2 Report Generation​

@Component
public class ReportTasks {

private final ReportService reportService;
private final EmailService emailService;

// Daily report at 6 AM
@Scheduled(cron = "0 0 6 * * *")
public void generateDailyReport() {
log.info("Generating daily report");

DailyReport report = reportService.generateDailyReport(LocalDate.now().minusDays(1));
emailService.sendReportAsync("admin@company.com", report);

log.info("Daily report generated and sent");
}

// Weekly report on Monday at 7 AM
@Scheduled(cron = "0 0 7 * * MON")
public void generateWeeklyReport() {
log.info("Generating weekly report");

WeeklyReport report = reportService.generateWeeklyReport();
emailService.sendReportAsync("management@company.com", report);
}
}

4.3 Data Synchronization​

@Component
public class SyncTasks {

private final ExternalApiService externalApi;
private final ProductRepository productRepository;

// Synchronize prices every 15 minutes
@Scheduled(fixedRate = 900000) // 15 minutes
public void syncPrices() {
log.info("Starting price sync");

try {
List<PriceUpdate> updates = externalApi.fetchPriceUpdates();

updates.forEach(update -> {
productRepository.updatePrice(update.getProductId(), update.getNewPrice());
});

log.info("Synced {} price updates", updates.size());
} catch (Exception e) {
log.error("Price sync failed", e);
}
}
}

4.4 Health Check​

@Component
public class HealthCheckTasks {

private final List<ExternalService> externalServices;
private final AlertService alertService;

// Check external services every minute
@Scheduled(fixedRate = 60000)
public void checkExternalServices() {
externalServices.forEach(service -> {
boolean healthy = service.healthCheck();

if (!healthy) {
log.warn("Service {} is unhealthy", service.getName());
alertService.sendAlert("Service " + service.getName() + " is down");
}
});
}
}

5. Error Handling​

5.1 Try-Catch in Tasks​

@Component
public class RobustScheduledTask {

@Scheduled(fixedRate = 60000)
public void robustTask() {
try {
performTask();
} catch (Exception e) {
log.error("Task failed, will retry on next schedule", e);
// Do not re-throw to avoid stopping scheduling
}
}
}

5.2 Global Error Handler​

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setErrorHandler(t -> {
log.error("Scheduled task error", t);
// Send alert, metrics, etc.
alertService.sendAlert("Scheduled task failed: " + t.getMessage());
});
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}

6. Conditional Control​

6.1 Enable/Disable by Configuration​

@Component
@ConditionalOnProperty(name = "app.scheduling.enabled", havingValue = "true")
public class ScheduledTasks {
// These tasks only run if app.scheduling.enabled=true
}

6.2 Control by Profile​

@Component
@Profile("!test") // Not in test environment
public class ProductionScheduledTasks {

@Scheduled(cron = "0 0 0 * * *")
public void productionOnlyTask() {
// Only runs in production
}
}

6.3 Leader Election (Cluster)​

@Component
public class ClusterAwareTask {

private final LockProvider lockProvider;

@Scheduled(cron = "0 0 * * * *")
@SchedulerLock(name = "hourlyTask", lockAtLeastFor = "PT5M", lockAtMostFor = "PT30M")
public void hourlyTask() {
// Only one instance in the cluster executes this task
log.info("Running hourly task on this instance");
}
}

7. Summary​

Annotations​

AnnotationUsage
@EnableSchedulingEnables scheduling
@EnableAsyncEnables async methods
@ScheduledMarks a scheduled method
@AsyncMarks an asynchronous method

@Scheduled Options​

OptionDescription
fixedRateInterval from start
fixedDelayInterval from end
cronCron expression
initialDelayInitial delay
zoneTimezone for Cron

Best Practices​

  1. Always handle exceptions in tasks
  2. Use dedicated thread pools
  3. Monitor task execution
  4. Avoid overly long tasks
  5. Use locking in cluster environments
  6. Log task start and end

Next Chapter: WebSockets

Back to Module 14