Skip to main content
Multithreading is an object utility that provides thread pools and utilities for executing tasks asynchronously in Minecraft mods.

Accessing Multithreading

import gg.essential.api.utils.Multithreading

// Multithreading is an object, use it directly
Multithreading.runAsync {
    // Your async code here
}
import gg.essential.api.utils.Multithreading;

// Use static methods in Java
Multithreading.runAsync(() -> {
    // Your async code here
});

Thread Pools

Multithreading provides two thread pools for different use cases:

pool

General-purpose thread pool executor for running async tasks.
val executor = Multithreading.pool

// Direct access to ThreadPoolExecutor
executor.execute {
    println("Running in thread: ${Thread.currentThread().name}")
}
Configuration:
  • Core pool size: 10 threads
  • Maximum pool size: 30 threads
  • Keep-alive time: 0 seconds
  • Queue: LinkedBlockingQueue (unbounded)
  • Thread naming: “Thread 1”, “Thread 2”, etc.

scheduledPool

Scheduled executor service for delayed and repeating tasks.
val scheduler = Multithreading.scheduledPool

// Direct access to ScheduledExecutorService
scheduler.schedule({ println("Delayed task") }, 5, TimeUnit.SECONDS)
Configuration:
  • Pool size: 10 threads
  • Thread naming: “Thread 1”, “Thread 2”, etc.

Running Async Tasks

runAsync

Execute a task asynchronously on the thread pool.
import gg.essential.api.utils.Multithreading

Multithreading.runAsync {
    // This runs on a background thread
    val data = fetchDataFromApi()
    println("Fetched: $data")
}
Parameters:
  • runnable: Runnable - The task to execute
Notes:
  • Tasks execute immediately (no delay)
  • Uses the general-purpose thread pool
  • Non-blocking operation

submit

Submit a task and receive a Future to track completion.
import java.util.concurrent.Future

val future: Future<*> = Multithreading.submit {
    // Long-running task
    Thread.sleep(5000)
    println("Task completed")
}

// Check if task is done
if (future.isDone) {
    println("Task finished")
}

// Wait for task to complete
future.get()
Parameters:
  • runnable: Runnable - The task to submit
Returns: Future<*> - A future representing the task execution

Scheduling Tasks

schedule (Single Execution)

Schedule a task to run once after a delay.
import java.util.concurrent.TimeUnit
import java.util.concurrent.ScheduledFuture

val future: ScheduledFuture<*> = Multithreading.schedule(
    { println("Delayed task executed") },
    delay = 5,
    unit = TimeUnit.SECONDS
)

// Cancel the scheduled task if needed
future.cancel(false)
Parameters:
  • r: Runnable - The task to execute
  • delay: Long - Delay before execution
  • unit: TimeUnit - Time unit for the delay
Returns: ScheduledFuture<*> - A future representing the scheduled task

schedule (Repeating Execution)

Schedule a task to run repeatedly at fixed intervals.
import java.util.concurrent.TimeUnit

val repeatingTask = Multithreading.schedule(
    r = { println("Tick: ${System.currentTimeMillis()}") },
    initialDelay = 0,
    delay = 1,
    unit = TimeUnit.SECONDS
)

// Cancel after some time
Thread.sleep(5000)
repeatingTask.cancel(false)
Parameters:
  • r: Runnable - The task to execute repeatedly
  • initialDelay: Long - Delay before first execution
  • delay: Long - Delay between subsequent executions
  • unit: TimeUnit - Time unit for delays
Returns: ScheduledFuture<*> - A future representing the scheduled task Notes:
  • Uses fixed-rate scheduling (not fixed-delay)
  • Executions may overlap if task takes longer than the delay

Object Reference

object Multithreading {
    val pool: ThreadPoolExecutor
    val scheduledPool: ScheduledExecutorService
    
    fun runAsync(runnable: Runnable)
    fun submit(runnable: Runnable): Future<*>
    fun schedule(r: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*>
    fun schedule(
        r: Runnable,
        initialDelay: Long,
        delay: Long,
        unit: TimeUnit
    ): ScheduledFuture<*>
}

Common Use Cases

Background API Requests

import gg.essential.api.utils.Multithreading
import gg.essential.api.utils.WebUtil

fun fetchPlayerData(uuid: String) {
    Multithreading.runAsync {
        // Runs off the main thread
        val json = WebUtil.fetchJSON("https://api.example.com/player/$uuid")
        val playerName = json.optString("name")
        
        // Update UI on main thread if needed
        println("Player: $playerName")
    }
}

Delayed Actions

import java.util.concurrent.TimeUnit

fun showDelayedMessage() {
    Multithreading.schedule(
        { println("This appears after 3 seconds") },
        delay = 3,
        unit = TimeUnit.SECONDS
    )
}

Periodic Tasks

import java.util.concurrent.TimeUnit
import java.util.concurrent.ScheduledFuture

class AutoSaver {
    private var saveTask: ScheduledFuture<*>? = null
    
    fun start() {
        saveTask = Multithreading.schedule(
            r = { performAutoSave() },
            initialDelay = 60,
            delay = 60,
            unit = TimeUnit.SECONDS
        )
    }
    
    fun stop() {
        saveTask?.cancel(false)
    }
    
    private fun performAutoSave() {
        println("Auto-saving...")
        // Save logic here
    }
}

Parallel Processing

import java.util.concurrent.Future

fun processMultipleItems(items: List<String>) {
    val futures = mutableListOf<Future<*>>()
    
    items.forEach { item ->
        val future = Multithreading.submit {
            processItem(item)
        }
        futures.add(future)
    }
    
    // Wait for all tasks to complete
    futures.forEach { it.get() }
    println("All items processed")
}

fun processItem(item: String) {
    println("Processing: $item")
    Thread.sleep(1000)
}

Async with Callback

fun asyncOperation(callback: (String) -> Unit) {
    Multithreading.runAsync {
        // Simulate long operation
        Thread.sleep(2000)
        val result = "Operation complete"
        
        // Invoke callback with result
        callback(result)
    }
}

// Usage
asyncOperation { result ->
    println("Result: $result")
}

Timeout Handling

import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

fun operationWithTimeout() {
    val future = Multithreading.submit {
        // Potentially long-running task
        Thread.sleep(10000)
        "Done"
    }
    
    try {
        // Wait max 5 seconds
        future.get(5, TimeUnit.SECONDS)
        println("Operation completed in time")
    } catch (e: TimeoutException) {
        println("Operation timed out")
        future.cancel(true)
    }
}

Debounced Actions

import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

class DebouncedAction(private val delayMs: Long, private val action: () -> Unit) {
    private var scheduledTask: ScheduledFuture<*>? = null
    
    fun trigger() {
        // Cancel previous scheduled execution
        scheduledTask?.cancel(false)
        
        // Schedule new execution
        scheduledTask = Multithreading.schedule(
            action,
            delayMs,
            TimeUnit.MILLISECONDS
        )
    }
}

// Usage
val debouncedSave = DebouncedAction(1000) {
    println("Saving...")
    saveConfig()
}

// Each call resets the timer
debouncedSave.trigger()
debouncedSave.trigger()  // Previous call is cancelled
debouncedSave.trigger()  // Only this one will execute after 1s

Thread-Safe GUI Updates

import gg.essential.api.utils.GuiUtil

fun loadDataAndUpdateGui() {
    Multithreading.runAsync {
        // Load data in background
        val data = loadHeavyData()
        
        // Update GUI on main thread
        GuiUtil.open(ResultsScreen(data))
    }
}

Best Practices

  1. Don’t block the main thread: Use runAsync for I/O operations, network requests, or heavy computations
  2. Handle exceptions: Wrap async code in try-catch blocks to prevent silent failures
Multithreading.runAsync {
    try {
        riskyOperation()
    } catch (e: Exception) {
        println("Error: ${e.message}")
    }
}
  1. Clean up scheduled tasks: Cancel repeating tasks when no longer needed
class MyMod {
    private var updateTask: ScheduledFuture<*>? = null
    
    fun onEnable() {
        updateTask = Multithreading.schedule(::update, 0, 20, TimeUnit.SECONDS)
    }
    
    fun onDisable() {
        updateTask?.cancel(false)
    }
    
    fun update() {
        // Periodic update logic
    }
}
  1. Use appropriate time units: Choose the most readable unit for your delays
// Good - clear and readable
Multithreading.schedule(task, 5, TimeUnit.MINUTES)

// Less clear
Multithreading.schedule(task, 300, TimeUnit.SECONDS)

Notes

  • Thread pools are shared across the application - avoid blocking operations
  • Scheduled tasks use fixed-rate execution, not fixed-delay
  • All threads are named sequentially: “Thread 1”, “Thread 2”, etc.
  • The general pool can grow to 30 threads maximum
  • Tasks are queued if all threads are busy
  • Always handle exceptions in async code to prevent thread pool contamination

Build docs developers (and LLMs) love