ByteLib Documentation 1.1 Help

ByteLib Bootstrapper

The ByteLib bootstrapper is the core of ByteLib. It is responsible for:

  • configuring Google Guice dependency injection

  • handling plugin lifecycle hooks

  • registering commands

  • initializing the plugin runtime

In short, ByteLib lives and dies by its bootstrapper.

Bootstrap a Plugin

Bootstrapping a plugin simply means configuring your plugin to run with ByteLib. Because Paper plugins define their bootstrapper and loader through configuration, this setup must be done manually.

If you want to use ByteLib, the ByteLib bootstrapper is mandatory. It cannot be replaced or modified.

Bootstrapping Overview

  1. Create a paper-plugin.yml and register ByteLib's bootstrapper and loader classes.

  2. Extend ByteLibPlugin in your plugin's main class.

  3. Create a Google Guice module that registers your plugin dependencies.

  4. Create one or more Lifecycle classes implementing PluginLifecycle.

  5. Create a wiring class to connect your Guice modules and lifecycle components.

Bootstrapping in Detail

1. Create paper-plugin.yml

Create the configuration file in:

src/main/resources/paper-plugin.yml

Example (from DimensionPause):

name: DimensionPause version: "2.0.0" main: org.reprogle.dimensionpause.DimensionPausePlugin api-version: "1.21" bootstrapper: org.reprogle.bytelib.BytePluginBootstrap loader: org.reprogle.bytelib.BytePluginLoader

Paper Plugin Concepts

bootstrapper

The bootstrapper controls how the plugin is initialized. It is responsible for:

  • configuring internal Guice modules

  • performing initial dependency injection

  • initializing ByteLib internals

The bootstrapper requires your plugin's main class to extend ByteLibPlugin.

loader

The loader defines the runtime environment your plugin loads into.

According to Paper documentation, the loader is used to:

  • construct the plugin classpath

  • provide external dependencies

ByteLib's loader currently adds BoostedYAML and Google Guice to the plugin classpath.

2. Extend ByteLibPlugin

Your plugin's main class must extend ByteLibPlugin.

public final class DimensionPausePlugin extends ByteLibPlugin { @Inject public DimensionPausePlugin(Injector injector, PluginMeta meta, Path dataDir, ComponentLogger logger) { super(injector, meta, dataDir, logger); } }

3. Create a Guice Module

Create a module responsible for configuring dependency bindings for your plugin. This module would be responsible for binding dependencies that are specific to your plugin.

Example (from DimensionPause):

public final class DimensionPauseModule extends AbstractModule { @Override protected void configure() { // Core services bind(CommandFeedback.class).in(Singleton.class); // Subcommands Multibinder<SubCommand> subcommandBinder = Multibinder.newSetBinder(binder(), SubCommand.class); subcommandBinder.addBinding().to(Reload.class); subcommandBinder.addBinding().to(State.class); subcommandBinder.addBinding().to(Toggle.class); // Lifecycle hooks Multibinder<PluginLifecycle> lifecycles = Multibinder.newSetBinder(binder(), PluginLifecycle.class); lifecycles.addBinding().to(DimensionPauseLifecycle.class); // Commands Multibinder.newSetBinder(binder(), CommandRegistration.class) .addBinding() .to(DPCommandRegistration.class); } }

4. Create a Lifecycle Class

Lifecycle classes allow you to execute code during plugin lifecycle events. Your lifecycle class must implement PluginLifecycle.

Just like Paper plugins, it can (but isn't required to) define onLoad, onEnable, and onDisable.

Example:

public class DimensionPauseLifecycle implements PluginLifecycle { private final ComponentLogger logger; @Inject public DimensionPauseLifecycle(ComponentLogger logger) { this.logger = logger; } @Override public void onEnable() { logger.info("Dimension Pause has been loaded"); } @Override public void onDisable() { logger.info("Dimension Pause is shutting down"); } }

5. Wire Guice Together

Finally, connect all modules and lifecycle components.

ByteLib supports three wiring conventions.

// Create a class named `[MainClass]Wiring.class` and implement PluginWiring // Ensure it returns your Module you made in step 3 public static class DimensionPauseWiring implements PluginWiring { @Override public List<Module> modules(PluginMeta meta, Path dataDir, ComponentLogger logger) { return List.of( new DimensionPauseModule() ); } }
// Nest a `Wiring` class in your plugin's main class public final class DimensionPausePlugin extends ByteLibPlugin { @Inject public DimensionPausePlugin(Injector injector, PluginMeta meta, Path dataDir, ComponentLogger logger) { super(injector, meta, dataDir, logger); } @SuppressWarnings("unused") public static class Wiring implements PluginWiring { @Override public List<Module> modules(PluginMeta meta, Path dataDir, ComponentLogger logger) { return List.of( new DimensionPauseModule() ); } } }
// Create a class that implements PluginWiring, and allow the `ServiceLoader` to attempt to locate it (Not recommended) public static class PluginWiringSetup extends PluginWiring { @Override public List<Module> modules(PluginMeta meta, Path dataDir, ComponentLogger logger) { return List.of( new DimensionPauseModule() ); } }

If this seems complex, that's because dependency injection introduces additional structure.

However, the benefit is strong decoupling, which makes large plugins significantly easier to maintain and test.

Next Steps

After bootstrapping your plugin, configure the remaining ByteLib systems:

10 March 2026