High-Performance, Flexible Scheduled Task Scheduling Framework
Bus Cron is a lightweight, high-performance scheduled task framework based on cron expressions. It provides a simple yet powerful API for task scheduling, supporting standard Linux crontab-style syntax while offering enterprise-grade features like task listeners, flexible scheduling patterns, and thread pool management.
- Cron Expression Support: Full support for standard 5-field cron expressions (minute, hour, day of month, month, day of week)
- Quartz Compatibility: Supports 6-field (with seconds) and 7-field (with year) expressions for Quartz compatibility
- Flexible Scheduling: Schedule tasks using cron patterns, Runnable tasks, or custom Crontab implementations
- Thread Pool Management: Configurable thread pool with daemon/non-daemon options
- Task Listeners: Monitor task lifecycle with start, success, and failure callbacks
| Feature | Description | Benefit |
|---|---|---|
| Second-Level Precision | Optional second matching mode | Sub-minute scheduling accuracy |
| Time Zone Support | Per-scheduler timezone configuration | Multi-region deployment support |
| Trigger Queue Mode | Pre-calculated trigger times | Improved scheduling performance |
| Task Metadata | ID-based task management | Dynamic task control |
| Configuration Loading | Load tasks from config files | Externalized task management |
Standard 5-Field Format:
* * * * *
│ │ │ │ │
│ │ │ │ └─ Day of Week (0-6, Sunday=0 or 7)
│ │ │ └─── Month (1-12)
│ │ └───── Day of Month (1-31)
│ └─────── Hour (0-23)
└───────── Minute (0-59)
6-Field Format (Quartz-compatible, includes seconds):
* * * * * *
│ │ │ │ │ │
│ │ │ │ │ └─ Day of Week
│ │ │ │ └─── Month
│ │ │ └───── Day of Month
│ │ └─────── Hour
│ └───────── Minute
└─────────── Second (0-59)
7-Field Format (includes year):
* * * * * * *
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ Year (1970-2099)
│ │ │ │ │ └─── (other fields same as 6-field)
<dependency>
<groupId>org.miaixz</groupId>
<artifactId>bus-tempus</artifactId>
<version>8.x.x</version>
</dependency>import org.miaixz.bus.tempus.Builder;
// Start the scheduler (loads config/tempus.setting if present)
Builder.start();
// Schedule a task to run every 5 minutes
Builder.schedule("*/5 * * * *", () -> {
System.out.println("Task executed at: " + new Date());
});
// Stop the scheduler
Builder.stop();String taskId = Builder.schedule("my-task-id", "0 * * * *", () -> {
System.out.println("Hourly task execution");
});
// Remove the task
Builder.remove(taskId);import org.miaixz.bus.tempus.Scheduler;
// Create a custom scheduler
Scheduler scheduler = new Scheduler();
// Configure and start
scheduler.setMatchSecond(true) // Enable second-level precision
.start(true); // Start as daemon thread
// Schedule a task
scheduler.schedule("task1", "0 */2 * * *", () -> {
System.out.println("Task runs every 2 hours");
});
// Stop the scheduler
scheduler.stop();// Every minute
"* * * * *"
// Every 5 minutes
"*/5 * * * *"
// At minute 5 of every hour
"5 * * * *"
// At 11:59 on Monday and Tuesday
"59 11 * * 1,2"
// Every 5 minutes between 3 and 18 (inclusive)
"3-18/5 * * * *"
// At 2:00 AM every Monday
"0 2 * * 1"
// Every second (requires matchSecond=true)
"* * * * * *"
// At 12:00 PM on the last day of every month
"0 12 L * *"
// At midnight on January 1st every year
"0 0 1 1 *"Scheduler scheduler = new Scheduler();
scheduler.start();
// Schedule a Runnable
scheduler.schedule("cleanup", "0 0 3 * * *", new Runnable() {
@Override
public void run() {
System.out.println("Running daily cleanup at 3 AM");
// Cleanup logic here
}
});import org.miaixz.bus.tempus.crontab.Crontab;
public class MyCustomTask implements Crontab {
@Override
public void execute() {
System.out.println("Executing custom task");
// Your task logic here
}
}
// Schedule the custom task
Scheduler scheduler = new Scheduler();
scheduler.schedule("custom", "0 * * * *", new MyCustomTask());
scheduler.start();import org.miaixz.bus.tempus.listener.TaskListener;
import org.miaixz.bus.tempus.Executor;
Scheduler scheduler = new Scheduler();
// Add a task listener
scheduler.addListener(new TaskListener() {
@Override
public void onStart(Executor executor) {
System.out.println("Task started: " + executor.task());
}
@Override
public void onSucceeded(Executor executor) {
System.out.println("Task completed successfully");
}
@Override
public void onFailed(Executor executor, Exception e) {
System.err.println("Task failed: " + e.getMessage());
}
});
scheduler.start();Create config/tempus.setting:
# Schedule tasks using class.method syntax
com.example.TaskScheduler.cleanup = 0 0 3 * * *
com.example.TaskScheduler.backup = 0 0 4 * * *
# Grouped tasks
[com.example.jobs]
dataSync = */30 * * * *
reportGeneration = 0 9 * * 1-5Load and execute:
import org.miaixz.bus.tempus.Builder;
import org.miaixz.bus.setting.Setting;
// Load from default locations (config/tempus.setting or tempus.setting)
Builder.start();
// Or load from custom path
Setting cronSetting = new Setting("path/to/custom-tempus.setting");
Scheduler scheduler = new Scheduler();
scheduler.schedule(cronSetting).start();Scheduler scheduler = new Scheduler();
scheduler.start();
// Add a task
String taskId = scheduler.schedule("temp-task", "*/10 * * * *", () -> {
System.out.println("Temporary task");
});
// Update the pattern
scheduler.updatePattern(taskId, new org.miaixz.bus.tempus.pattern.CronPattern("*/5 * * * *"));
// Remove the task
scheduler.deschedule(taskId);
// Get task info
Crontab task = scheduler.getTask(taskId);
CronPattern pattern = scheduler.getPattern(taskId);
// Check scheduler status
boolean isEmpty = scheduler.isEmpty();
int taskCount = scheduler.size();import org.miaixz.bus.tempus.Configure;
import org.miaixz.bus.tempus.Scheduler;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Create custom configuration
Configure config = Configure.of()
.setTimeZone(TimeZone.getTimeZone("America/New_York"))
.setMatchSecond(true)
.setDaemon(false)
.setUseTriggerQueue(true);
// Create scheduler with config
Scheduler scheduler = new Scheduler(config);
// Set custom thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);
scheduler.setThreadExecutor(executor);
// Add listener
scheduler.addListener(new TaskListener() {
// Implement listener methods
});
// Start
scheduler.start();import org.miaixz.bus.tempus.pattern.CronPattern;
import java.util.Date;
// Create a pattern
CronPattern pattern = new CronPattern("0 * * * *");
// Check if a time matches
boolean matches = pattern.match(new Date(), false);
// Get next matching time
long nextMatch = pattern.nextMatchFromNow();
// Find all matches in a range
Date start = new Date();
Date end = new Date(start.getTime() + 86400000); // 24 hours later
List<Date> matches = CronPattern.matchedDates("0 * * * *", start, end, 24);
// Validate an expression
boolean isValid = Builder.isValidExpression("0 * * * *");// For most cases, minute precision is sufficient
Builder.setMatchSecond(false);
Builder.schedule("*/5 * * * *", task);
// Only enable second precision if needed
Builder.setMatchSecond(true);
Builder.schedule("*/30 * * * * *", task); // Every 30 seconds// Good: Handle exceptions within the task
Builder.schedule("0 * * * *", () -> {
try {
// Task logic
performOperation();
} catch (Exception e) {
logger.error("Task failed", e);
}
});
// Better: Use task listeners
scheduler.addListener(new TaskListener() {
@Override
public void onFailed(Executor executor, Exception e) {
logger.error("Task failed: {}", executor.task(), e);
}
});// Always specify IDs for tasks that might be managed dynamically
String taskId = scheduler.schedule("backup-task", "0 2 * * *", backupTask);
// Later, you can update or remove it
scheduler.updatePattern(taskId, newPattern);
scheduler.deschedule(taskId);// For CPU-intensive tasks
ExecutorService cpuPool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
scheduler.setThreadExecutor(cpuPool);
// For I/O-intensive tasks
ExecutorService ioPool = Executors.newCachedThreadPool();
scheduler.setThreadExecutor(ioPool);// Daemon: Tasks terminate immediately when scheduler stops
scheduler.start(true);
// Non-Daemon: Tasks complete execution even after scheduler stops
scheduler.start(false);// Schedule at 9:30 AM every day
Scheduler scheduler = new Scheduler();
scheduler.schedule("daily-930", "30 9 * * *", () -> {
System.out.println("Task runs at 9:30 AM daily");
});
scheduler.start();// Run only on weekdays (Monday-Friday)
scheduler.schedule("weekdays", "0 9 * * 1-5", task);
// Run on Monday, Wednesday, and Friday
scheduler.schedule("mwf", "0 10 * * 1,3,5", task);
// Run on weekends
scheduler.schedule("weekends", "0 12 * * 6,0", task);// Using Builder
Builder.setMatchSecond(true);
Builder.start();
// Using Scheduler
Scheduler scheduler = new Scheduler();
scheduler.setMatchSecond(true);
scheduler.start();// Stop and keep tasks
scheduler.stop();
// Stop and clear all tasks
scheduler.stop(true);
// Wait for running tasks to complete
scheduler.stop(false);scheduler.addListener(new SimpleTaskListener() {
@Override
public void onFailed(Executor executor, Exception e) {
// Log the error
logger.error("Task failed: {}", executor.task(), e);
// Send alert
alertService.send("Task execution failed", e);
// Retry logic
if (isRetryable(e)) {
scheduleRetry(executor.task());
}
}
});// Yes! Month names (case-insensitive)
"0 9 * Jan * " // 9 AM every day in January
"0 12 * Apr-Jun *" // Noon every day from April to June
// Day of week names
"0 9 * * Mon" // 9 AM every Monday
"0 9 * * Mon,Fri" // 9 AM every Monday and Friday// Use 'L' for last day of month
scheduler.schedule("last-day", "0 0 L * *", () -> {
System.out.println("Last day of the month");
});// Daemon mode: JVM exits even if tasks are running
scheduler.start(true);
// Non-daemon mode: JVM waits for tasks to complete
scheduler.start(false);Use daemon mode for background tasks that can be interrupted, and non-daemon mode for critical tasks that must complete.
| Bus Cron Version | JDK Version |
|---|---|
| 8.x | 17+ |
| 7.x | 11+ |
| Option | Type | Default | Description |
|---|---|---|---|
timezone |
TimeZone |
TimeZone.getDefault() |
Time zone for task scheduling |
matchSecond |
boolean |
false |
Whether to enable second-level precision |
daemon |
boolean |
false |
Whether to use daemon threads |
useTriggerQueue |
boolean |
false |
Whether to use trigger queue mode |
| Character | Description | Example |
|---|---|---|
* |
Match all values | * * * * * (every minute) |
? |
Same as * |
? * ? * ? |
/ |
Step values | */5 * * * * (every 5 minutes) |
- |
Range | 0 9-17 * * * (9 AM to 5 PM) |
, |
List | 0 9,12,18 * * * (9 AM, noon, 6 PM) |
L |
Last day | 0 0 L * * (last day of month) |
// By default, tasks run independently
// To prevent overlapping executions:
public class IsolatedTask implements Crontab {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
@Override
public void execute() {
if (!isRunning.compareAndSet(false, true)) {
logger.warn("Previous execution still running, skipping");
return;
}
try {
// Task logic
} finally {
isRunning.set(false);
}
}
}// Create scheduler for specific timezone
Configure config = Configure.of()
.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
Scheduler scheduler = new Scheduler(config);
// Pattern matches in Shanghai time
scheduler.schedule("shanghai-task", "0 9 * * *", task);// Validate before scheduling
String expression = "0 0 12 * * *"; // Invalid: has seconds but matchSecond=false
if (Builder.isValidExpression(expression)) {
Builder.schedule(expression, task);
} else {
logger.error("Invalid cron expression: {}", expression);
}void start() // Start scheduler
void start(boolean isDaemon) // Start with daemon setting
void stop() // Stop scheduler
void restart() // Restart scheduler
String schedule(String pattern, Crontab task) // Schedule task
String schedule(String id, String pattern, Crontab task) // Schedule with ID
boolean remove(String schedulerId) // Remove task
void setMatchSecond(boolean isMatchSecond) // Set second precisionScheduler start() // Start scheduler
Scheduler start(boolean isDaemon) // Start with daemon setting
Scheduler stop() // Stop scheduler
Scheduler stop(boolean clearTasks) // Stop and optionally clear
Scheduler schedule(String id, String pattern, Crontab) // Schedule task
Scheduler deschedule(String id) // Remove task
Scheduler updatePattern(String id, CronPattern) // Update pattern
Crontab getTask(String id) // Get task
CronPattern getPattern(String id) // Get pattern
int size() // Get task count
boolean isEmpty() // Check if empty
Scheduler clear() // Clear all tasksboolean match(long millis, boolean isMatchSecond) // Check if time matches
boolean match(Calendar calendar, boolean isMatchSecond) // Check if calendar matches
long nextMatchFromNow() // Get next match time
Calendar nextMatchAfter(Calendar calendar) // Get next match after time
static List<Date> matchedDates(...) // Find all matches in rangeFor more information and updates, visit: https://github.com/818000/bus