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 49619c6 · 2026-09-13 12:21 UTC

Skip to content

Conditional Registration

Since v6.2.0

The @ConditionalOnConfig annotation is available starting from UltiTools-API v6.2.0.

UltiTools allows you to conditionally register components based on YAML configuration values. This lets server admins enable or disable features without requiring code changes.

Basic Usage

Add @ConditionalOnConfig to any component class (@Service, @CmdExecutor, @EventListener):

java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.annotations.ConditionalOnConfig;
import com.ultikits.ultitools.annotations.command.CmdExecutor;
import org.bukkit.command.CommandSender;

@CmdExecutor(alias = {"warp"}, permission = "myplugin.command.warp")
@ConditionalOnConfig(value = "config/config.yml", path = "enableWarp")
public class WarpCommands extends BaseCommandExecutor {
    // Only registered if enableWarp: true in config.yml

    @Override
    protected void handleHelp(CommandSender sender) {
        sender.sendMessage("/warp");
    }
}

The corresponding YAML:

yaml
# config/config.yml
enableWarp: true

If enableWarp is false or missing, the WarpCommands class is not registered at all — no command, no memory usage, no side effects.

Only the connector entry point still skips the condition

The standard @UltiToolsModule path resolves command classes as container beans, so a class whose condition is false was never constructed as a bean, and this already worked before v6.3.0. As of v6.3.0 the listener package-scan path evaluates the condition too, closing the one gap that was real. Only the connector entry point (PluginManager.register(UltiToolsPlugin)) performs no component scan at all, so nothing there evaluates the condition yet -- tracked in issue #334.

Reload Drift Reporting

@ConditionalOnConfig is evaluated once, at component scan (plugin startup). ul reload re-reads the config file, but it never registers or unregisters anything by itself -- it only reports what changed.

As of v6.3.0, the drift message names what the container actually holds, not just what the condition now answers:

[UltiTools-API] @ConditionalOnConfig drift after reload: com.example.MyService (config/config.yml -> myFeature.enabled) now evaluates to disabled, but the component is already registered. @ConditionalOnConfig is evaluated once at component scan; a restart is required to remove the component.

Before v6.3.0, the advice was one fixed sentence

The message used to advise "a restart is required" unconditionally, even when nothing had ever been constructed. As of v6.3.0 the advice follows what the container actually holds: present and now disabled restarts to remove it, absent and now disabled needs no restart (check the startup log instead), absent and now enabled restarts to create it, and present and now enabled needs no restart either.

Annotation Attributes

AttributeTypeDefaultDescription
valueString(required)Config file path relative to the plugin data folder
pathString(required)Dot-separated or slash-separated YAML key path
negatebooleanfalseIf true, register when config value is false (inverted logic)

Examples

Conditional Service

java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.annotations.ConditionalOnConfig;
import com.ultikits.ultitools.annotations.Scheduled;
import com.ultikits.ultitools.annotations.Service;

@Service
@ConditionalOnConfig(value = "config/config.yml", path = "economy.enabled")
public class EconomyService {

    @Scheduled(period = 36000, async = true)
    public void distributeTax() {
        // Only runs if economy.enabled: true
    }
}

Conditional Event Listener

java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.annotations.ConditionalOnConfig;
import com.ultikits.ultitools.annotations.EventListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

@EventListener
@ConditionalOnConfig(value = "config/config.yml", path = "welcomeMessage.enabled")
public class WelcomeListener implements Listener {

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        event.getPlayer().sendMessage("Welcome to the server!");
    }
}

Nested Config Keys

Use dots or slashes for nested keys:

yaml
# config/config.yml
features:
  teleport:
    enabled: true
  pvp:
    enabled: false
java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.annotations.ConditionalOnConfig;
import com.ultikits.ultitools.annotations.command.CmdExecutor;
import org.bukkit.command.CommandSender;

@CmdExecutor(alias = {"tp"}, permission = "myplugin.teleport")
@ConditionalOnConfig(value = "config/config.yml", path = "features.teleport.enabled")
public class TeleportCommands extends BaseCommandExecutor {

    @Override
    protected void handleHelp(CommandSender sender) {
        sender.sendMessage("/tp");
    }
}

Inverted Logic with negate

Use negate = true to register a component when the config value is false:

java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.annotations.ConditionalOnConfig;
import com.ultikits.ultitools.annotations.Service;

@Service
@ConditionalOnConfig(value = "config/config.yml", path = "maintenance", negate = true)
public class NormalModeService {
    // Only active when maintenance: false (or missing)
}

Complete Example

A plugin with optional features controlled by config:

yaml
# config/config.yml
features:
  home: true
  warp: true
  economy: false
  welcome: true
java
package com.ultikits.docs.conditional;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.UltiToolsModule;

@UltiToolsModule(scanBasePackages = {"com.ultikits.docs.conditional"})
public class MyPlugin extends UltiToolsPlugin {
    @Override
    public boolean registerSelf() { return true; }

    @Override
    public void unregisterSelf() { }
}
java
@CmdExecutor(alias = {"home"}, permission = "myplugin.home")
@ConditionalOnConfig(value = "config/config.yml", path = "features.home")
public class HomeCommands extends BaseCommandExecutor {
    // Registered (features.home = true)

    @Override
    protected void handleHelp(CommandSender sender) { }
}

@CmdExecutor(alias = {"warp"}, permission = "myplugin.warp")
@ConditionalOnConfig(value = "config/config.yml", path = "features.warp")
public class WarpCommands extends BaseCommandExecutor {
    // Registered (features.warp = true)

    @Override
    protected void handleHelp(CommandSender sender) { }
}

@Service
@ConditionalOnConfig(value = "config/config.yml", path = "features.economy")
public class EconomyService {
    // NOT registered (features.economy = false)
}

@EventListener
@ConditionalOnConfig(value = "config/config.yml", path = "features.welcome")
public class WelcomeListener implements Listener {
    // Registered (features.welcome = true)
}

Before v6.2.0

Without @ConditionalOnConfig, developers had to manually check config values in registerSelf() and conditionally register components with if statements. The annotation approach is cleaner and eliminates boilerplate.

Contributors

No contributors

Released under the MIT License.