Runtime

Runtime

MagicRuntime is the managed service container behind the bootstrap-first setup. It gives you one place to access core services, register extra components, manage closeable resources, and rebuild config-backed clients on reload.

Why a runtime container

Without a container, a plugin ends up with a handful of static singletons or fields that each service reaches into, and shutdown becomes a manual list of "close this, then that, in the right order" that is easy to get wrong on reload or when a startup step fails halfway.

MagicRuntime holds the wired services (config, logger, lang, and anything you add), hands them out by type or by name, and closes everything it manages in reverse order when the plugin stops. Your shared code depends on one runtime handle instead of on a specific platform. It also rebuilds config-backed clients for you when the config reloads, so services stay in sync without teardown boilerplate.

Getting A Runtime

The recommended path is buildRuntime():

java
BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this)        .enableCommands()        .buildRuntime();MagicRuntime runtime = magic.runtime();

The same pattern exists for every bootstrap helper:

  • BukkitBootstrap
  • BungeeBootstrap
  • VelocityBootstrap
  • FabricBootstrap
  • NeoForgeBootstrap

Custom platforms can also build MagicRuntime manually.

Core Components

Every runtime starts with typed components for the core services:

  • Platform
  • ConfigManager
  • LoggerCore
  • LanguageManager when configured

Access them via the typed component registry:

java
Platform platform = runtime.requireComponent(Platform.class);ConfigManager configManager = runtime.requireComponent(ConfigManager.class);LoggerCore logger = runtime.requireComponent(LoggerCore.class);CommandRegistry commands = runtime.findComponent(CommandRegistry.class).orElse(null);

Use findComponent(...) when the component is optional and requireComponent(...) when its absence is a bug.

Typed Components

Register and replace typed components at runtime:

java
runtime.putComponent(MyService.class, new MyService());MyService service = runtime.requireComponent(MyService.class);Optional<MyService> opt = runtime.findComponent(MyService.class);

findComponent(...) also matches assignable types, so requesting an interface will find a registered implementation.

Inspecting And Reacting To State

Read the current registries or react when they change:

java
Map<Class<?>, Object> typed = runtime.components();Map<String, Object> named = runtime.namedComponents();runtime.onStateChanged(() -> logger.debug("Runtime components changed"));if (runtime.isClosed()) {    return; // don't touch a closed runtime}

components() and namedComponents() return snapshots of the typed and named registries. onStateChanged(...) fires after any component registry update, and isClosed() guards against using a runtime after shutdown.

Named Components

MagicRuntime also exposes a named registry for dynamic resources:

java
runtime.putNamedComponent("service.cache", cacheClient);CacheClient cache = runtime.requireNamedComponent("service.cache", CacheClient.class);Optional<CacheClient> opt = runtime.findNamedComponent("service.cache", CacheClient.class);

Remove a named component when it is no longer needed:

java
runtime.removeNamedComponent("service.cache");

findNamedComponent(name) also has an untyped overload returning Optional<Object> when you do not need a type check.

Named components are especially useful for:

  • reloadable clients
  • plugin-owned service singletons
  • resources keyed by logical role (http.monitoring, ws.gateway)

Managed Resources

Use resource(...) when you want a stable named slot that closes replaced resources automatically:

java
MagicRuntimeResource<MagicHttpClient> monitoring = runtime.resource(        "http.monitoring",        MagicHttpClient.builder(runtime.platform(), runtime.configManager())                .baseUrl("https://api.example.com/")                .build());MagicHttpClient client = monitoring.require();monitoring.set(MagicHttpClient.builder(runtime.platform(), runtime.configManager())        .baseUrl("https://api-two.example.com/")        .build());

The resource is also exposed through the named component registry under the same name.

Config Bindings

Use bindConfig(...) when a closeable resource should rebuild automatically on matching config reloads:

java
MagicRuntimeConfigBinding<ServiceConfig, MagicHttpClient> binding = runtime.bindConfig(        "http.monitoring",        ServiceConfig.class,        config -> MagicHttpClient.builder(runtime.platform(), runtime.configManager())                .baseUrl(config.monitoring.baseUrl)                .build(),        "monitoring");MagicHttpClient client = binding.require();

The binding is a small handle around the managed resource:

  • require() / current() — the current resource, throwing or as an Optional.
  • configClass() — the bound config type; name() — the resource name.
  • resource() — the underlying MagicRuntimeResource.
  • isClosed() / close() — lifecycle state and manual teardown.

This pattern works well for:

  • HTTP clients
  • WebSocket clients
  • database pools
  • SDK clients

The HTTP client module also provides higher-level wrappers:

  • MagicHttpClientProfile
  • MagicWebSocketClientProfile

Use those when the resource being managed is specifically an HTTP or WebSocket client.

Lifecycle

MagicRuntime.close():

  • unregisters its platform shutdown hook when one was installed
  • closes managed resources in reverse registration order
  • closes runtime resources and config bindings
  • can shut down ConfigManager automatically

Bootstrap helpers already configure the runtime so that magic.runtime().close() is the one shutdown call you usually need in your plugin or mod.

You can register extra shutdown actions on a live runtime, not only through the builder:

java
runtime.onClose("metrics", metrics::flush);

Actions registered this way run during close() alongside managed resources, in reverse registration order.

Building A Runtime Manually

For NeoForge or custom platforms, build it directly:

java
MagicRuntime runtime = MagicRuntime.builder(platform, configManager, logger)        .languageManager(languageManager)        .component(MyPlugin.class, this)        .manage("database", databaseClient)        .onClose("metrics", metrics::flush)        .manageConfigManager(true)        .autoRegisterShutdown(true)        .build();

Builder controls:

  • languageManager(...)
  • component(...)
  • manage(...)
  • onClose(...)
  • manageConfigManager(...)
  • autoRegisterShutdown(...)

Disable autoRegisterShutdown(...) when the platform already has an explicit shutdown phase you want to own manually.