Unreleased version v6.3.0-SNAPSHOT. This page describes the alpha branch and may change at any time; it is not part of any released version.

commit 0ed0d39 · 2026-10-06 00:56 UTC

Skip to content

Module Load Ordering ​

Ordering takes effect as of v6.3.0

@PluginDependency has existed since v6.2.0 as declarative metadata with no reader. As of v6.3.0, both it and plugin.yml's loadAfter participate in the actual load order.

Declaring dependencies ​

Add @PluginDependency to the class that extends UltiToolsPlugin:

java
@UltiToolsModule(scanBasePackages = {"com.example.myplugin"})
@PluginDependency(
    depends = {"CorePlugin", "UtilsPlugin"},
    softDepends = {"OptionalPlugin"}
)
public class MyPlugin extends UltiToolsPlugin {
    // ...
}
AttributeMeaning
dependsRequired. Missing at load time refuses this module.
softDependsOptional. This module loads after these if they are present; their absence does not refuse it.
loadBeforeThe inverse declaration: names modules that should load after this one.

plugin.yml's loadAfter ​

plugin.yml's own loadAfter: list is read into the same dependency graph as @PluginDependency, resolved by module name:

yaml
# plugin.yml
loadAfter:
  - CorePlugin

No loadAfter attribute was added to @PluginDependency itself. The two mechanisms are kept separate on purpose: plugin.yml already has loadAfter, and @PluginDependency already has depends/softDepends/loadBefore, so declare an ordering constraint through whichever of the two already expresses it.

Cycles and missing dependencies ​

A dependency cycle, or a depends entry naming a module that is not installed, refuses only the affected modules — the cycle members (or the module with the missing dependency) and anything that transitively depends on them. Every unrelated module still loads.

The console names the full cycle as a path, A -> B -> C -> A, and says which module's author to contact. This mirrors how Paper itself reports a plugin load cycle.

As of v6.3.0, a module declaring several missing depends targets at once is named all of them in one console line, not just the first — you no longer need to fix one, restart, and discover the next.

A required server plugin is missing ​

As of v6.3.0, a module whose plugin.yml lists a server plugin under depend: that is not installed is refused before it is constructed, with one console line and no stack trace, for example [UltiTools-API] Module 'UltiTools-Economy' requires Vault, which is not installed or not enabled; the module is not loaded. Nothing of the module runs: it is not constructed, no resource is extracted and no class is scanned. This also refuses a module that lists an uninstalled plugin under depend: but never used that plugin's classes while loading, which used to load anyway; Bukkit itself refuses a plugin whose depend: is missing.

A required plugin that is installed but not enabled yet is not refused early, because it may still be enabled after UltiTools. In that case the module is refused only if loading fails because that plugin's classes are missing, with the same single line. Any other load failure, including one while every depend: plugin is present, still prints the Cannot initialize plugin for … line with its trace (issue #554).

Duplicate plugin.yml names ​

As of v6.3.0: dependency resolution looks a module up by either its simple class name or its plugin.yml name: value. If two installed modules declare the SAME name:, only one of them can claim that name in the dependency graph — whichever module is discovered first wins, and the console logs a WARNING naming both modules and the shared name so this is not silent. Which module wins depends on load order (currently filesystem-dependent — not something this page's ordering rules control), so treat this WARNING as a signal to rename one module's plugin.yml name:, not as something to rely on resolving the same way every restart.

Falling back to the legacy order ​

-Dultitools.useLegacyPluginLoading=true restores the pre-6.3.0 behavior: every module loads in filesystem order with no dependency resolution at all, including no cycle detection. This is a JVM system property, consumed once at server startup, not a reloadable config key — the same shape as Paper's own -Dpaper.useLegacyPluginLoading=true.

Cost of the legacy switch

With dependency resolution skipped entirely, a module that relies on load order may fail to initialize, unpredictably, with no error naming the cause. Use this switch only as a temporary escape hatch while fixing the underlying cycle or missing dependency.

Contributors

No contributors

Released under the MIT License.