The ShutdownHookUtil interface provides a safe way to run cleanup code when Minecraft is closing, without directly using Java’s Runtime.addShutdownHook().
Getting ShutdownHookUtil
val shutdownHook = EssentialAPI.getShutdownHookUtil()
ShutdownHookUtil shutdownHook = EssentialAPI.getShutdownHookUtil();
Registering shutdown tasks
register()
Register a Runnable to be executed when the game closes.
shutdownHook.register {
println("Game is closing, running cleanup...")
// Your cleanup code here
}
shutdownHook.register(() -> {
System.out.println("Game is closing, running cleanup...");
// Your cleanup code here
});
The task to run on shutdown
Shutdown tasks are executed synchronously when the game closes. Keep them fast to avoid delaying shutdown.
Unregistering shutdown tasks
unregister()
Remove a previously registered Runnable from the shutdown hook list.
val cleanupTask = Runnable {
println("Cleanup")
}
// Register
shutdownHook.register(cleanupTask)
// Later, unregister if no longer needed
shutdownHook.unregister(cleanupTask)
Runnable cleanupTask = () -> {
System.out.println("Cleanup");
};
// Register
shutdownHook.register(cleanupTask);
// Later, unregister if no longer needed
shutdownHook.unregister(cleanupTask);
You must pass the exact same Runnable instance to unregister() that was passed to register(). Lambda expressions create new instances, so store the task in a variable if you need to unregister it later.
Example: Resource cleanup
import gg.essential.api.EssentialAPI
import java.io.Closeable
class ResourceManager : Closeable {
private val resources = mutableListOf<Closeable>()
private val shutdownTask: Runnable
init {
// Create shutdown task
shutdownTask = Runnable {
cleanup()
}
// Register it
EssentialAPI.getShutdownHookUtil().register(shutdownTask)
}
fun addResource(resource: Closeable) {
resources.add(resource)
}
private fun cleanup() {
println("Cleaning up ${resources.size} resources...")
for (resource in resources) {
try {
resource.close()
} catch (e: Exception) {
println("Failed to close resource: ${e.message}")
}
}
resources.clear()
}
override fun close() {
// Unregister shutdown hook when manually closed
EssentialAPI.getShutdownHookUtil().unregister(shutdownTask)
cleanup()
}
}
Example: Saving configuration
import gg.essential.api.EssentialAPI
import java.io.File
class ConfigManager {
private val configFile = File("config/mymod.json")
private var config = loadConfig()
init {
// Register save on shutdown
EssentialAPI.getShutdownHookUtil().register {
saveConfig()
}
}
private fun loadConfig(): Config {
// Load configuration from file
return if (configFile.exists()) {
Config.fromJson(configFile.readText())
} else {
Config()
}
}
private fun saveConfig() {
println("Saving configuration...")
configFile.parentFile.mkdirs()
configFile.writeText(config.toJson())
println("Configuration saved")
}
fun updateSetting(key: String, value: Any) {
config.set(key, value)
// Config will be saved automatically on shutdown
}
}
Best practices
Keep shutdown tasks fast
Shutdown tasks run synchronously and block the game from closing. Keep them as fast as possible:
// Good: Fast, essential cleanup
shutdownHook.register {
configManager.save()
connectionPool.close()
}
// Bad: Slow, non-essential operations
shutdownHook.register {
// Don't do this - too slow!
uploadStatistics()
downloadUpdates()
sendAnalytics()
}
Store task references for unregistering
If you need to unregister a task later, store it in a variable:
class MyManager {
private val shutdownTask = Runnable {
cleanup()
}
fun enable() {
EssentialAPI.getShutdownHookUtil().register(shutdownTask)
}
fun disable() {
EssentialAPI.getShutdownHookUtil().unregister(shutdownTask)
cleanup()
}
}
Handle exceptions gracefully
Always handle exceptions in shutdown tasks to avoid breaking other shutdown hooks:
shutdownHook.register {
try {
riskyCleanupOperation()
} catch (e: Exception) {
println("Cleanup failed: ${e.message}")
e.printStackTrace()
}
}
Why use ShutdownHookUtil?
Using ShutdownHookUtil instead of Runtime.addShutdownHook() provides several benefits:
- Coordination - Essential can manage shutdown order
- Error handling - Built-in exception handling and logging
- Testability - Easier to test shutdown behavior
- Consistency - All Essential-related shutdown hooks in one place
Use ShutdownHookUtil for cleanup tasks that must run when the game closes, like saving configuration or closing network connections. For optional tasks, consider running them earlier (e.g., when leaving a world).
See also
Source: api/src/main/kotlin/gg/essential/api/utils/ShutdownHookUtil.kt