Skip to Content
1robie

Troubleshooting

Concrete failures — the symptom, what causes it, and the fix.

Grouped by what you actually see.

Build and startup

NoClassDefFoundError: fr/robie/paperdispatch/...

The library is not in your jar. It is not installed on the server, so compileOnly (Gradle) or provided (Maven) is wrong for it — use implementation / default scope and a shade step. See Shading & relocation.

If you are already shading, you are probably shipping the plain jar output instead of the shaded one. Check:

unzip -l target/yourplugin.jar | grep paperdispatch

ClassNotFoundException only from the built jar

Shadow's minimize() strips classes it cannot see being used, including ones reached reflectively. Build once without it to confirm, then add explicit excludes rather than dropping it entirely.

JitPack returns 404 for the version

Either the tag does not exist yet — the version in the project's pom.xml is the next, unreleased one — or the group id is wrong. It is com.github.1robie.paper-dispatch, because the project is multi-module; com.github.1robie:paper-dispatch never resolves. Check the build log before assuming the coordinates are wrong: JitPack compiles a new tag on first request.

The server fails to start on Spigot

Expected. There is no Bukkit fallback — see Supported platforms.

IllegalArgumentException at command construction

Two causes, both from validation that is deliberately loud:

  • The argument name uses the reserved flag prefix. Value flags generate synthetic argument names sharing a prefix (flag$ by default). Rename the argument, or change the prefix with setFlagValuePrefix — before adding any arguments, since a late change would invalidate names that already passed validation.
  • A flag name or alias collides with one already registered on that command.

Commands do not appear

Nothing registered at all

Check that you called both halves:

this.commands.trackCommand(new HealCommand(this));
this.commands.flushRegistrations();     // easy to forget

trackCommand only records the command; flushRegistrations is what reaches the server. On the flush you should see Registering N commands via lifecycle events... in the log.

Failed to register command '<name>'

The server rejected it, and the cause is on the same log line — usually a name the server will not accept, or a tree that was mutated after being built.

Aliases missing: the server declined these aliases: …

Another plugin or a vanilla command already claimed them. The command itself registered fine, and the namespaced form (/yourplugin:name) still works. Rename the alias if you want the short form.

Could not find the 'allowsLifecycleRegistration' field

The manager probes that field to choose between lifecycle and dynamic registration. Seeing this means Paper changed internals — commands may still register via the dynamic path, but treat it as a signal to update the library.

Nodes silently missing from a command

Anything added to a builder after it has been attached to a parent is discarded without an error: Brigadier's then builds the child immediately and stores an immutable node. Finish configuring a node before passing it to addSubCommand.

Behaviour at runtime

A sub-command's alias does not work under the parent's alias

/g new is not built when /guild has alias g and create has alias new. Aliases are one level deep on purpose — the full cross-product would be real nodes sent to every client. See Sub-commands.

/cmd --flag sub says unknown command

Flags must come last. Flag nodes branch only into other flag nodes, so /cmd sub --flag parses and the reverse does not.

An optional argument is empty when a value was given

getOptionalArgument returns empty if the value matches a registered flag name — that is what stops /heal --silent capturing silent as the optional target. The side effect is that a player genuinely named like one of your flags cannot be passed positionally. dispatch.isFlagLikeValue(...) exposes the same check so you can explain it.

A sub-command shadows an argument value

Brigadier resolves literals before arguments, so a sub-command named list wins over a string argument in the same position, and that value never reaches the argument.

IllegalArgumentException from getArgument

Brigadier throws the same exception for "no such argument" and "wrong type", so this is either a typo in the name or a mismatched class. Probe with hasArgument(name), which checks presence only — getOptionalArgument deliberately still throws on a type mismatch rather than hiding the bug.

The command runs but the player is null

getSenderAsPlayer() is @Nullable even with setPlayerOnly() set. Null-check it rather than relying on a requirement staying in place. Console and command blocks are the usual sources.

A player without permission sees "Unknown command"

That is how requirements work — they are Brigadier requires predicates evaluated before parsing, so the command is hidden rather than refused. To explain the refusal instead, check inside perform. See Requirements & permissions.

Permission changes do not take effect immediately

The command tree is per-source and sent when the server decides to send it. A permission granted at runtime applies the next time that player receives the tree.

Tab-completion

Offline player names do not complete

No cache is installed. OfflinePlayerCache.install(this) in onEnable, and uninstall in onDisable. Without one, OfflinePlayerArgument rejects every value with a "no offline player cache is installed" error and suggests nothing.

A cache is built but the argument still ignores it

A cache from builder(...).build() is not the global instance, and OfflinePlayerArgument reads the global one. Use install, or accept that the argument will not see your cache.

Completion is truncated

maxSuggestions bounds it, on purpose: the name index is unbounded, so an empty prefix would otherwise serialise every known player name on every keystroke. Raise it via the builder if you must.

Mojang API rate-limited us while refreshing …

A rename refresh was throttled. The stale name is kept and the refresh retried later; nothing is broken. The same applies to Failed to reach Mojang API … when Mojang is unreachable.

Performance

Startup warning about flag count

More than four flags on one command. The warning reports the node count the flag subtree will occupy — growth is faster than n!, and the tree ships to every client. Collapse booleans into one value flag, or call setFlagCountWarnThreshold(FLAG_COUNT_WARN_DISABLED) once you have measured and accepted the cost.

Thread pool leak across reloads

A cache you built yourself was never closed. Each OfflinePlayerCache owns an HttpClient with its own threads. Call close() in onDisable, or use install / uninstall.

Still stuck

Open an issue on the repository with the server version, the library version and the relevant log lines.

Last updated on

On this page