Documentation
Scheduler
Schedule synchronous and asynchronous tasks to run on the server thread or in the background.
The "Scheduler" API
The Scheduler object provides a convenient way to schedule tasks in your scripts, wrapping the Bukkit scheduler with automatic cleanup and error handling.
Use it to run code synchronously on the server thread, asynchronously in the background, or repeatedly at fixed intervals.
Understanding Sync vs Async
Synchronous (Sync) tasks run on the main server thread and can safely interact with the Bukkit API (players, worlds, entities, blocks, etc.). Most Bukkit operations must be performed synchronously.
Asynchronous (Async) tasks run on a separate thread pool and should not interact with the Bukkit API directly. Use async tasks for heavy computation, database queries, web requests, or file I/O that would otherwise freeze the server.
⚠️ Important: Never modify game state (players, worlds, entities) from async tasks. If you need to update game state after async work, schedule a sync task to apply the changes.
Running Tasks Immediately
Run a sync task with Scheduler.runSync(callback)
Executes a task on the next server tick on the main thread.
Scheduler.runSync(() => { console.log('This runs on the main server thread'); Bukkit.broadcastMessage('§aTask executed! '); });
Run an async task with Scheduler.runAsync(callback)
Executes a task immediately on a background thread.
Scheduler.runAsync(() => { // Safe: Heavy computation const result = performExpensiveCalculation(); console.log(`Calculation result: ${result}`); // NOT SAFE: Don't interact with Bukkit API here! // Bukkit.broadcastMessage('Done!'); // ❌ This will cause errors! });
Running Tasks with a Delay
Run a sync task later with Scheduler.runSyncLater(callback, delayInTicks)
Executes a task on the main thread after a specified delay.
// Wait 5 seconds (100 ticks) before running Scheduler.runSyncLater(() => { Bukkit.broadcastMessage('§e5 seconds have passed!'); }, 100); // Teleport player after 3 seconds Scheduler.runSyncLater(() => { const player = Bukkit.getPlayer('Notch'); if (player !== null) { player.teleport(spawnLocation); } }, 60);
Run an async task later with Scheduler.runAsyncLater(callback, delayInTicks)
Executes a task on a background thread after a specified delay.
// Save data to file after 10 seconds Scheduler.runAsyncLater(() => { savePlayerDataToFile(); console.log('Player data saved asynchronously'); }, 200);
Running Repeating Tasks
Run a sync repeating task with Scheduler.runSyncTimer(callback, delayInTicks, periodInTicks)
Executes a task repeatedly on the main thread at fixed intervals.
Parameters:
callback- The function to run repeatedlydelayInTicks- Initial delay before the first executionperiodInTicks- Delay between each execution (period)
// Broadcast a message every minute const taskId = Scheduler.runSyncTimer(() => { Bukkit.broadcastMessage('§6Reminder: Vote for the server!'); }, 0, 20 * 60); // 0 delay, run every 1200 ticks (60 seconds) // Update player scoreboards every second Scheduler.runSyncTimer(() => { Bukkit.getOnlinePlayers().forEach(player => { updateScoreboard(player); }); }, 0, 20); // 0 delay, run every 20 ticks (1 second)
Run an async repeating task with Scheduler.runAsyncTimer(callback, delayInTicks, periodInTicks)
Executes a task repeatedly on a background thread at fixed intervals.
// Check database for updates every minute Scheduler.runAsyncTimer(() => { const updates = fetchDatabaseUpdates(); console.log(`Found ${updates.length} updates`); }, 0, 20 * 60); // Every minute // Auto-save to file every 5 minutes Scheduler.runAsyncTimer(() => { saveWorldDataToFile(); console.log('Auto-save completed'); }, 20 * 60 * 5, 20 * 60 * 5); // 5 minute delay, repeat every 5 minutes
Cancelling Tasks
All scheduler methods return a task ID that can be used to cancel the task later.
Cancel a task with Scheduler.cancel(taskId)
// Start a repeating task const taskId = Scheduler.runSyncTimer(() => { console.log('This repeats forever... or does it?'); }, 0, 20); // Cancel it after 10 seconds Scheduler.runSyncLater(() => { Scheduler.cancel(taskId); console.log('Task cancelled! '); }, 200);
Practical Examples
Countdown timer
function startCountdown(seconds) { let remaining = seconds; const taskId = Scheduler.runSyncTimer(() => { if (remaining <= 0) { Scheduler.cancel(taskId); Bukkit.broadcastMessage('§c§lTIME\'S UP!'); return; } Bukkit.broadcastMessage(`§e${remaining} seconds remaining...`); remaining--; }, 0, 20); // Run immediately, then every second } startCountdown(10);
Delayed teleport with cancellation
const teleportTasks = new Map(); function teleportWithDelay(player, location, seconds) { const playerId = player.getUniqueId().toString(); // Cancel existing teleport if any if (teleportTasks.has(playerId)) { Scheduler.cancel(teleportTasks.get(playerId)); } player.sendMessage(`§eTeleporting in ${seconds} seconds... Don't move!`); const startLocation = player.getLocation(); const taskId = Scheduler.runSyncLater(() => { // Check if player moved if (player.getLocation().distance(startLocation) > 0.5) { player.sendMessage('§cTeleport cancelled - you moved! '); teleportTasks.delete(playerId); return; } player.teleport(location); player.sendMessage('§aTeleported! '); teleportTasks. delete(playerId); }, seconds * 20); teleportTasks.set(playerId, taskId); }
Async data processing with sync callback
function processPlayerData(player) { player.sendMessage('§eProcessing your data...'); // Do heavy work asynchronously Scheduler.runAsync(() => { const data = performExpensiveCalculation(player. getUniqueId()); // Switch back to sync to interact with player Scheduler.runSync(() => { player.sendMessage(`§aProcessing complete! Result: ${data}`); player.giveExp(100); }); }); }
Periodic task with player updates
// Update all players' action bars every minute Scheduler.runAsyncTimer(() => { Bukkit.getOnlinePlayers().forEach(player => { updatePlayerStats(player); }); }, 0, 20 * 60); // Every minute (1200 ticks)
Batch processing with delay
function processBatch(items, batchSize, delayBetweenBatches) { let index = 0; const taskId = Scheduler.runSyncTimer(() => { const batch = items.slice(index, index + batchSize); if (batch.length === 0) { Scheduler.cancel(taskId); console.log('Batch processing complete'); return; } batch.forEach(item => processItem(item)); index += batchSize; }, 0, delayBetweenBatches); } // Process 10 items every 2 seconds processBatch(myLargeArray, 10, 40);
Tick Timing Reference
Minecraft servers run at 20 ticks per second (TPS) under normal conditions.
- 1 tick = 50 milliseconds
- 20 ticks = 1 second
- 100 ticks = 5 seconds
- 200 ticks = 10 seconds
- 1200 ticks = 60 seconds (1 minute)
- 24000 ticks = 20 minutes (1 Minecraft day)
// Common timing constants const SECOND = 20; const MINUTE = 20 * 60; const HOUR = 20 * 60 * 60; Scheduler.runSyncTimer(() => { saveData(); }, 0, 5 * MINUTE); // Every 5 minutes
Automatic Cleanup
All tasks scheduled through the Scheduler API are automatically cancelled when your script is unloaded or reloaded.
This prevents orphaned tasks from continuing to run after a script update and helps avoid memory leaks.
You don't need to manually track and cancel tasks during script cleanup - the runtime handles this for you.
Method Aliases
The Scheduler API provides multiple method names for convenience and Bukkit API familiarity:
runTask()→runSync()runTaskLater()→runSyncLater()runTaskAsynchronously()→runAsync()runTaskLaterAsynchronously()→runAsyncLater()runTaskTimer()→runSyncTimer()runTaskTimerAsynchronously()→runAsyncTimer()
Use whichever naming convention you prefer - they all work identically.
Register event listeners to respond to Bukkit events in your scripts.
Execute SQL queries and manage database operations with automatic connection pooling and error handling.