API Specstable
6 min read time

Documentation

Scheduler

Schedule synchronous and asynchronous tasks to run on the server thread or in the background.

apischedulertaskstiming

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 repeatedly
  • delayInTicks - Initial delay before the first execution
  • periodInTicks - 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.

Documentation in early stages. Not meant for public consumption.