Skip to main content

The Lifecycle, End to End

This page is the map. It shows the whole pipeline ModuleKit runs, from "some classes on the classpath" to "a running application", and links to the deep-dive page for each stage. Read it once to see how the pieces fit; then follow the links to understand each mechanism in detail.

The pipeline

discoverbuild graphtopologicalsortinjectonLoadonEnablefind modulesprovides→requiresorder + cyclesconstructregister servicesgo live
StageWhat happensDeep dive
discoverRead every META-INF/services/...Module file, load the classes, resolve each ModuleDescriptor.Discovery
build graphMap each provided type to its provider; turn every requires into a dependency edge; flag missing providers and duplicate providers.Dependency Graph
topological sortOrder modules so providers precede consumers; detect cycles; break ties using system() priority.Topological Sort
injectFind the constructor matching the requires set and call it with the services from the registry.Dependency Injection
onLoadEach module registers the services it provides and does one-time setup.Lifecycle & States
onEnable / onDisableActivate / tear down. Disable runs in reverse order.Lifecycle & States

The thing that orchestrates all of this — storing modules, running actions over them in order — is the Module Manager.

Who calls what

A crucial detail: the core does not drive the lifecycle by itself. The base ModuleManager gives you discovery, storage, and two primitives — runAction (forward order) and runActionReversed (reverse order). The adapter decides what the phases are and calls those primitives.

Host — your plugin / main()discover() · runLoad() · runEnable() · runDisable()ModuleManager (core) — the adapter drives the phasesrunAction(...) forward · runActionReversed(...) reverseInjectionResolverconstructs each moduleDependencyGraphcomputes load order + faultsServiceLoaderDiscovery · TopologicalSort · ModuleContext

This is why there are two adapters that behave differently (three phases on Paper, two on Minestom) while sharing the exact same engine. See Writing Your Own Adapter.

The division of labour

modulekit-api the words everyone agrees on
Module, ModuleDescriptor, LoadContext — interfaces & records only

modulekit-core the machinery
ServiceLoaderDiscovery finds modules
DependencyGraph resolves who-depends-on-whom
TopologicalSort computes a safe order (Kahn's algorithm)
InjectionResolver constructs modules with their dependencies
ModuleManager stores contexts, runs ordered actions
ModuleContext per-module state holder

modulekit-paper PaperModule, PaperModuleManager, PaperLoadContext,
CommandRegistration, ModuleAwareCommand
→ maps the lifecycle onto Bukkit's JavaPlugin

modulekit-minestom MinestomModule, MinestomModuleManager, MinestomLoadContext
→ a standalone manager for a Minestom bootstrap

Every class named above has its own explanation in these Core Concepts pages or in the Adapters section. Nothing is left as "just call this."

Fault isolation is a first-class idea

At every stage a module can be marked faulty instead of crashing the run:

  • discover: no readable descriptor.
  • build graph: a required service has no provider, or two modules provide the same type.
  • topological sort: the module is part of a dependency cycle.
  • inject: no matching constructor, an ambiguous one, or one that throws.
  • onLoad: the module calls ctx.markFaulty(reason).

A faulty module is set aside and skipped in every later phase. Crucially, it does not stop unrelated modules from loading. Only modules that depend on the faulty one are affected (their required service never appears, so they fault too). This is covered in Lifecycle & States.

What ModuleKit deliberately leaves out

ModuleKit has exactly one responsibility: discover modules, resolve their dependencies, inject them, and manage their lifecycle. It intentionally does not provide:

Not includedWhere it belongs
Event busInside a module, or the host platform (Bukkit/Minestom already have one).
Config file handlingEach module reads its own config in onLoad.
Database / ORM layerExpose one as a provided service from a module.
Messaging / i18nA module's concern.
Permission checksYour commands and listeners.

The one exception is Paper command registration, which the Paper adapter owns — because guarding a command by module state genuinely needs to live in the framework. See the Paper adapter.

Keeping the surface small is a feature: the behaviour is predictable and easy to reason about. You bring the features; ModuleKit assembles them.

Continue

Start the deep dive at Modules & Descriptors — the vocabulary everything else is built on.