Tutorial 42: Scheduler ⏰
📋 Table of Contents
- Understanding the Question
- Solution Approach
- Complete Implementation
- Important Considerations
- Practice Questions
1. Understanding the Question ❓
What are we trying to achieve?
- What is Scheduling? - Executing tasks at specific times or intervals
- Why? - Background jobs like data cleanup, report generation, notifications
- When? - Tasks that don't need immediate user interaction
- How? - @Scheduled annotation with cron or fixed intervals
- Best practices - Use appropriate thread pools, avoid blocking operations, monitor execution
The Problem It Solves
Without Scheduling:
// User-triggered cleanup on every request
@GetMapping("/users")
public List<User> getUsers() {
deleteExpiredSessions(); // Blocks API response!
generateDailyReports(); // More delays!
return userRepository.findAll();
}
// API endpoint becomes slow due to background workWith Scheduling:
// Background task runs automatically at midnight
@Scheduled(cron = "0 0 0 * * ?")
public void dailyCleanup() {
deleteExpiredSessions();
generateDailyReports();
}
// API endpoint remains fast!
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll(); // Fast response
}2. Solution Approach 🎯
Definition
Scheduling in Spring Boot allows you to execute code at specific times or intervals using @Scheduled annotation and cron expressions.
Scheduling Types
Fixed Rate - Run every N milliseconds
Fixed Delay - Wait N ms after previous execution completes
Cron Expression - Run at specific times (0 0 0 * * ? = daily at midnight)3. Complete Implementation 💻
Example 1: Basic Scheduling
Configuration
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling // Enable @Scheduled annotations
public class SchedulingConfig {
// Enables all @Scheduled methods
}Scheduled Tasks
package com.example.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class ScheduledTasks {
/**
* Run every 5 seconds (5000 milliseconds)
* Waits 5 seconds BETWEEN task completions
*/
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
log.info("Task running every 5 seconds");
}
/**
* Wait 5 seconds AFTER task completes before running again
* Good for operations where you want spacing
*/
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
log.info("Task waiting 5 seconds after completion");
}
/**
* Run at specific time using cron expression
* "0 0 0 * * ?" = Every day at midnight
*/
@Scheduled(cron = "0 0 0 * * ?")
public void dailyMidnightTask() {
log.info("Running daily maintenance");
cleanupExpiredData();
generateDailyReports();
}
/**
* Cron: "0 0 * * * ?" = Every hour
* Cron: "0 */15 * * * ?" = Every 15 minutes
* Cron: "0 0 9-17 * * MON-FRI" = 9AM-5PM weekdays
*/
@Scheduled(cron = "0 */15 * * * ?")
public void quarterhourlyTask() {
log.info("Running every 15 minutes");
}
/**
* Configure in application.properties instead
* Allows changing schedule without code changes
*/
@Scheduled(cron = "${app.schedule.cleanup-cron:0 0 * * * ?}")
public void configurableTask() {
log.info("Schedule from properties");
}
/**
* Can have initial delay before first execution
*/
@Scheduled(initialDelay = 5000, fixedRate = 10000)
public void taskWithInitialDelay() {
log.info("Runs 5 seconds after startup, then every 10 seconds");
}
}Example 2: Service Layer Scheduled Tasks
package com.example.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class MaintenanceService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditLogRepository auditRepository;
/**
* Run every day at 2 AM to clean up expired sessions
* Cron: min hour day month dayOfWeek
* 0 2 * * * = 02:00 every day
*/
@Scheduled(cron = "0 2 * * * *")
public void cleanupExpiredSessions() {
int deleted = userRepository.deleteExpiredSessions();
System.out.println("Deleted " + deleted + " expired sessions");
}
/**
* Send email reminders every evening at 6 PM
*/
@Scheduled(cron = "0 18 * * * *")
public void sendDailyReminders() {
List<User> inactiveUsers = userRepository.findInactive(30);
inactiveUsers.forEach(user ->
emailService.sendReminder(user.getEmail(), "We miss you!")
);
}
/**
* Run weekly on Monday at midnight to generate reports
* 0 = seconds, 0 = minutes, 0 = hours, * = day, * = month, 1 = Monday
*/
@Scheduled(cron = "0 0 0 * * 1")
public void generateWeeklyReports() {
List<ReportData> data = collectMetrics();
generateReport(data);
emailService.sendWeeklyReport(data);
}
/**
* Archive old audit logs every Sunday at 3 AM
*/
@Scheduled(cron = "0 3 0 * * 0")
public void archiveOldLogs() {
int archived = auditRepository.archiveLogsOlderThan(90);
System.out.println("Archived " + archived + " log entries");
}
/**
* Monitor system health every 5 minutes
*/
@Scheduled(fixedRate = 300000) // 300,000 ms = 5 minutes
public void monitorSystemHealth() {
// Check database connection
// Check memory usage
// Check disk space
// Alert if issues found
}
}Example 3: Thread Pool Configuration
Configuration
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@EnableScheduling
public class SchedulingConfig {
/**
* Configure custom thread pool for scheduled tasks
* Default uses single thread (can cause delays!)
*/
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 10 concurrent tasks
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
scheduler.initialize();
return scheduler;
}
}application.properties
# Configure scheduled task thread pool
spring.task.scheduling.pool.size=10
spring.task.scheduling.thread-name-prefix=task-
spring.task.execution.pool.core-size=10
spring.task.execution.pool.max-size=20
spring.task.execution.pool.queue-capacity=100Example 4: Scheduled Task with Error Handling
package com.example.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
@Slf4j
@Component
@RequiredArgsConstructor
public class RobustScheduledTask {
private final NotificationService notificationService;
private final AuditService auditService;
/**
* Scheduled task with comprehensive error handling
*/
@Scheduled(fixedRate = 60000)
public void processWithErrorHandling() {
try {
log.info("Starting scheduled task");
// Main task logic
doHeavyWork();
log.info("Scheduled task completed successfully");
auditService.log("Task success");
} catch (Exception e) {
log.error("Error in scheduled task", e);
auditService.log("Task failed: " + e.getMessage());
notificationService.alertOps("Scheduled task failed: " + e);
}
}
/**
* Long-running task with timeout
*/
@Scheduled(fixedRate = 300000)
public void longRunningTask() {
long startTime = System.currentTimeMillis();
long timeout = 240000; // 4 minute timeout
try {
while (!isCompleted()) {
if (System.currentTimeMillis() - startTime > timeout) {
log.warn("Task timeout after 4 minutes");
break;
}
processNextBatch();
}
} catch (Exception e) {
log.error("Long running task failed", e);
}
}
}6. Important Considerations ⚠️
Best Practices
1. Avoid Blocking Operations
✅ DO: Use async tasks for long operations
@Scheduled(fixedRate = 60000)
public void quickTask() {
asyncService.processDataAsync(); // Returns immediately
}
❌ DON'T: Block scheduled thread
@Scheduled(fixedRate = 60000)
public void slowTask() {
heavyDatabaseQuery(); // Blocks scheduler!
saveAllToFile(); // More blocking!
}
📝 WHY: Blocking ties up scheduler thread, delays other tasks2. Use Appropriate Cron Expressions
✅ DO: Use correct cron syntax
0 0 0 * * ? # Midnight daily
0 0 */6 * * ? # Every 6 hours
0 9-17 * * MON-FRI # 9AM-5PM weekdays
❌ DON'T: Use incorrect expressions
0 0 0 * * # Missing day-of-week field
*/5 * * * * * # Six fields (Spring uses 6)
📝 WHY: Bad cron prevents task from running3. Monitor Scheduled Tasks
✅ DO: Log task execution and metrics
@Scheduled(fixedRate = 60000)
public void monitoredTask() {
long start = System.nanoTime();
try {
doWork();
} finally {
long duration = (System.nanoTime() - start) / 1_000_000;
logger.info("Task completed in {} ms", duration);
metrics.recordTaskDuration(duration);
}
}
❌ DON'T: Run unmonitored background tasks
// No visibility into task health
📝 WHY: Monitoring helps detect performance issuesCommon Pitfalls
Pitfall 1: Single Threaded Scheduler Causing Delays
[!CAUTION] Problem
javaundefined
// Two long tasks scheduled @Scheduled(fixedRate = 60000) public void longTask1() { Thread.sleep(50000); // Takes 50 seconds }
@Scheduled(fixedRate = 60000) public void longTask2() { Thread.sleep(50000); // Waits for longTask1! }
// longTask2 runs after longTask1 completes (single thread)
> [!SUCCESS] Solution
> ```java
// Configure multiple scheduler threads
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // 5 concurrent threads
scheduler.initialize();
return scheduler;
}
// Now both tasks run concurrentlyExplanation: Default Spring scheduler uses single thread. Configure thread pool to run tasks concurrently.
Pitfall 2: Missing Error Handling
[!CAUTION] Problem
javaundefined
@Scheduled(fixedRate = 60000) public void taskThatMayFail() { // If exception occurs, task stops silently int x = 1 / 0; // ArithmeticException // No notification that task failed! }
> [!SUCCESS] Solution
> ```java
@Scheduled(fixedRate = 60000)
public void safeTask() {
try {
int x = 1 / 0;
} catch (Exception e) {
logger.error("Task failed", e);
alertService.notifyOps("Scheduled task error: " + e);
}
}Explanation: Always wrap scheduled tasks in try-catch and log failures.
8. Practice Questions 📝
Question 1: Fixed Rate vs Fixed Delay
Q: What's the difference?
A: Fixed Rate (fixedRate = 5000):
- Runs every 5 seconds regardless of execution time
- If task takes 2s: next starts at 5s
- Good for frequent checks (every 5 mins)
Fixed Delay (fixedDelay = 5000):
- Waits 5 seconds AFTER completion
- If task takes 2s: waits 5s, next starts at 7s
- Good for rate limitingQuestion 2: Cron expression for business hours
Q: Schedule task 9 AM - 5 PM weekdays
A: "0 * 9-17 * * MON-FRI"
min hr hour-range day month day-of-week
0 * 9-17 * * MON-FRI
Every hour from 9AM to 5PM on weekdaysQuestion 3: Configure thread pool for 3 concurrent tasks
Q: How do you ensure tasks run in parallel?
A:
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(3); // 3 concurrent threads
scheduler.initialize();
return scheduler;
}
Or in properties:
spring.task.scheduling.pool.size=3🎯 Key Takeaways
- ✅ Use @Scheduled for background tasks - Runs independently of requests
- ✅ Configure thread pool - Default single thread can cause delays
- ✅ Add error handling - Always catch exceptions in scheduled tasks
- ✅ Use cron for time-based scheduling - More flexible than fixed intervals
- ✅ Monitor task execution - Log duration and failures
- ✅ Avoid blocking operations - Don't hold up scheduler threads
Changelog
- 2025-11-23: Initial creation with cron and thread pool examples
- Added: Error handling and monitoring patterns
Congratulations! You now master Scheduling! 🎉