Skip to content

Plugin Lifecycle

Hytale plugins follow a well-defined lifecycle with distinct states and phases. Understanding this lifecycle is essential for properly initializing resources, registering handlers, and cleaning up when your plugin shuts down.

A plugin transitions through the following states during its lifecycle:

flowchart LR
    NONE --> SETUP --> START --> ENABLED --> SHUTDOWN --> DISABLED
    SETUP -->|error| FAILED
    START -->|error| FAILED
    ENABLED -->|error| FAILED
    SHUTDOWN -->|error| FAILED
    DISABLED -->|"reload (new instance)"| NONE
StateDescription
NONEInitial state before any lifecycle methods are called
SETUPPlugin is executing its setup() method
STARTPlugin is executing its start() method
ENABLEDPlugin is fully active and operational
SHUTDOWNPlugin is executing its shutdown() method
DISABLEDPlugin has been shut down cleanly
FAILEDPlugin encountered an error and was marked as failed

Your plugin class should override these methods to participate in the lifecycle:

public class MyPlugin extends JavaPlugin {
public MyPlugin(JavaPluginInit init) {
super(init);
// DO NOT register anything here!
// Only store references if needed
}
}
@Override
@Nullable
public CompletableFuture<Void> preLoad() {
// Called before setup() to load configurations asynchronously
return super.preLoad();
}

This method is called before setup() and is primarily used for loading configuration files. If you use withConfig() to register configuration codecs, their loading happens here automatically.

Timing: Called asynchronously before the plugin enters SETUP state.

@Override
protected void setup() {
// Register components, systems, commands, events, etc.
getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect);
getCommandRegistry().registerCommand(new MyCommand());
getEntityStoreRegistry().registerSystem(new MySystem());
}

This is the primary initialization method where you should:

  • Register event listeners
  • Register commands
  • Register ECS components and systems
  • Register block states
  • Register asset types
  • Set up any plugin infrastructure

State: Plugin transitions from NONE to SETUP during this method.

@Override
protected void start() {
// Plugin is about to become fully active
// Perform any post-setup initialization
getLogger().at(Level.INFO).log("MyPlugin has started successfully!");
}

Called after setup() completes successfully. Use this for:

  • Logging startup messages
  • Starting background tasks
  • Connecting to external services
  • Any initialization that depends on all registrations being complete

State: Plugin transitions from SETUP to START, then to ENABLED after this method returns.

@Override
protected void shutdown() {
// Clean up resources before the plugin is disabled
// Note: Registered handlers are automatically unregistered
}

Called when the plugin is being disabled. Use this for:

  • Saving data
  • Closing connections
  • Releasing external resources

State: Plugin transitions to SHUTDOWN during this method, then to DISABLED.

Plugins can declare configuration files that are automatically loaded during preLoad():

public class MyPlugin extends JavaPlugin {
private final Config<MyConfig> config;
public MyPlugin(JavaPluginInit init) {
super(init);
// Register config BEFORE setup (in constructor or field initializer)
this.config = withConfig(MyConfig.CODEC);
}
@Override
protected void setup() {
// Config is loaded and ready to use
MyConfig cfg = config.get();
if (cfg.isFeatureEnabled()) {
// ...
}
}
}
MethodDescription
withConfig(BuilderCodec<T> codec)Register a config using default name “config”
withConfig(String name, BuilderCodec<T> codec)Register a config with a custom name

Configuration files are stored in <data-directory>/<name>.json and are automatically loaded before setup() is called.

The following registries are available through your plugin base class:

MethodDescription
getCommandRegistry()Register commands
getEventRegistry()Register event listeners
getEntityStoreRegistry()Register entity ECS components and systems
getChunkStoreRegistry()Register chunk ECS components and systems
getBlockStateRegistry()Register custom block states
getEntityRegistry()Register entity types
getTaskRegistry()Register scheduled tasks
getAssetRegistry()Register asset types
getClientFeatureRegistry()Register client feature flags
getCodecRegistry(codec)Register to codec maps
MethodReturn TypeDescription
getLogger()HytaleLoggerPlugin-specific logger
getDataDirectory()PathPlugin’s data folder
getIdentifier()PluginIdentifierPlugin’s unique identifier
getManifest()PluginManifestPlugin’s manifest data
getBasePermission()StringRoot permission node (group.name)
getFile()PathPath to plugin JAR (JavaPlugin only)
isEnabled()booleanReturns true if state is SETUP, START, or ENABLED
isDisabled()booleanReturns true if state is NONE, DISABLED, SHUTDOWN, or FAILED
getState()PluginStateCurrent lifecycle state
package com.example.myplugin;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
import java.util.logging.Level;
public class MyPlugin extends JavaPlugin {
private static MyPlugin instance;
public static MyPlugin getInstance() {
return instance;
}
public MyPlugin(JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
instance = this;
getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect);
getCommandRegistry().registerCommand(new MyCommand());
getLogger().at(Level.INFO).log("MyPlugin setup complete!");
}
@Override
protected void start() {
getLogger().at(Level.INFO).log("MyPlugin v%s started!", getManifest().getVersion());
}
@Override
protected void shutdown() {
getLogger().at(Level.INFO).log("MyPlugin shutting down...");
instance = null;
}
private void onPlayerConnect(PlayerConnectEvent event) {
getLogger().at(Level.INFO).log("Player connected: %s", event.getPlayerRef().getUsername());
}
}

If an exception occurs during setup() or start():

  1. The exception is logged with the plugin’s logger
  2. The plugin transitions to FAILED state
  3. Registrations are cleaned up when the plugin is later shut down by the plugin manager
@Override
protected void setup() {
try {
// Risky initialization
} catch (Exception e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to initialize");
throw e; // Re-throw to trigger plugin disable
}
}
  1. Register in setup(): Always perform registrations during the setup phase
  2. Validate in start(): Use the start phase to verify all dependencies are available
  3. Clean up in shutdown(): Release any external resources (files, connections, etc.)
  4. Don’t block: Avoid long-running operations in lifecycle methods
  5. Use the logger: Log important events for debugging
  6. Check state: Use isEnabled() before accessing plugin resources from external code