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:23 UTC

Skip to content

External Plugin API

Since v6.2.2

Available in UltiTools-API 6.2.2 and later.

The External Plugin API allows any regular Bukkit/Spigot plugin to use UltiTools framework features without extending UltiToolsPlugin. Your plugin stays a normal JavaPlugin — just call UltiToolsAPI.connect(this) and the framework scans your package for @Service, @CmdExecutor, @EventListener, @Scheduled, and more.

Quick Start

1. Add Dependency

Maven

xml
<dependency>
    <groupId>com.ultikits</groupId>
    <artifactId>UltiTools-API</artifactId>
    <version>6.2.5</version>
    <scope>provided</scope>
</dependency>

2. Declare Dependency in plugin.yml

yaml
name: MyExternalPlugin
version: '1.0.0'
main: com.example.myplugin.MyExternalPlugin
depend: [UltiTools]

WARNING

depend: [UltiTools] is required. UltiTools must be loaded before your plugin so the framework is ready when connect() is called.

3. Connect and Disconnect

java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

public class MyExternalPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        UltiToolsAPI.connect(this);
        // Your plugin is now wired into UltiTools!
    }

    @Override
    public void onDisable() {
        UltiToolsAPI.disconnect(this);
    }
}

That's it. The framework automatically scans com.example.myplugin (derived from your main class) for annotated classes.

What Gets Scanned

When connect() is called, the framework scans your plugin's base package and registers:

AnnotationWhat it does
@ServiceRegistered as a bean in the IoC container
@CmdExecutorCommand class auto-registered with Bukkit
@EventListenerListener auto-registered with Bukkit
@ScheduledMethods scheduled as Bukkit tasks
@PlayerCacheFields tracked per-player with auto save/load
@ModuleEventHandlerSubscribed to the UltiTools EventBus

Writing Services

Services work exactly like in UltiTools modules — use @Service and @Autowired:

java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

@Service
public class GreetingService {

    @Autowired
    private StatsService statsService; // Injected by the IoC container

    public void greetPlayer(Player player) {
        player.sendMessage("Welcome! You have " + statsService.getVisits(player) + " visits.");
    }
}

Writing Commands

Use @CmdExecutor and @CmdMapping just like a UltiTools module:

java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

@CmdExecutor(alias = {"greet"}, permission = "myplugin.greet", description = "Greet command")
@CmdTarget(CmdTarget.CmdTargetType.PLAYER)
public class GreetCommand extends BaseCommandExecutor {

    @Autowired
    private GreetingService greetingService;

    @CmdMapping(format = "")
    public void greet(@CmdSender Player player) {
        greetingService.greetPlayer(player);
    }

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

TIP

Command descriptions use the raw description attribute from @CmdExecutor. The i18n() method is not available for external plugins — use plain strings.

Writing Event Listeners

java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

@EventListener
public class JoinListener implements Listener {

    @Autowired
    private GreetingService greetingService;

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        greetingService.greetPlayer(event.getPlayer());
    }
}

Data Storage

Use UltiToolsAPI.getDataOperator() to get a DataOperator for your data entities. Data is stored in your plugin's own data folder, not UltiTools' folder.

java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

@Data @Builder @NoArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Table("player_stats")
public class StatsEntity extends BaseDataEntity<String> {
    @Column("player_id") private String playerId;
    @Column("visits") private int visits;
}
java
package com.ultikits.docs.external;

import com.ultikits.ultitools.abstracts.command.BaseCommandExecutor;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.annotations.command.*;
import com.ultikits.ultitools.api.UltiToolsAPI;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.UUID;

@Service
public class StatsService {

    private final DataOperator<StatsEntity> dataOp;

    public StatsService() {
        JavaPlugin plugin = JavaPlugin.getPlugin(MyExternalPlugin.class);
        this.dataOp = UltiToolsAPI.getDataOperator(plugin, StatsEntity.class);
    }

    public int getVisits(Player player) {
        StatsEntity stats = dataOp.query()
            .where("player_id").eq(player.getUniqueId().toString())
            .first();
        return stats != null ? stats.getVisits() : 0;
    }
}

TIP

The storage backend (JSON, SQLite, or MySQL) is determined by the UltiTools server configuration, not your plugin. Your code works the same regardless. Query column names must still match the entity's @Column value, not the Java field name.

EventBus

Use UltiToolsAPI.getEventBus() for inter-plugin communication:

java
// Publish an event (MyCustomEvent must extend ModuleEvent)
UltiToolsAPI.getEventBus().publish(new MyCustomEvent(data));

// Subscribe via annotation in a @Service class
@ModuleEventHandler
public void onCustomEvent(MyCustomEvent event) {
    // Handle event from any module or external plugin
    // A handler parameter that does not extend ModuleEvent is skipped with a WARNING log, not registered
}

API Reference

MethodDescription
UltiToolsAPI.connect(JavaPlugin)Connect plugin to UltiTools framework
UltiToolsAPI.disconnect(JavaPlugin)Disconnect plugin from framework
UltiToolsAPI.isConnected(JavaPlugin)Check if a plugin is connected
UltiToolsAPI.getDataOperator(JavaPlugin, Class)Get a DataOperator scoped to the plugin's data folder
UltiToolsAPI.getEventBus()Get the shared EventBus instance

Lifecycle

  • Auto-disconnect: If your plugin is disabled (server stop, /reload), UltiTools automatically disconnects it via a PluginDisableEvent listener. You don't strictly need disconnect() in onDisable(), but it's good practice.
  • UltiTools shutdown: When UltiTools itself shuts down, all external plugins are disconnected automatically via disconnectAll().
  • Cleanup: On disconnect, all commands, listeners, scheduled tasks, EventBus subscriptions, and PlayerCache beans registered by your plugin are removed.

Differences from UltiTools Modules

FeatureUltiTools ModuleExternal Plugin
Base classextends UltiToolsPluginextends JavaPlugin
Registration@UltiToolsModule + registerSelf()UltiToolsAPI.connect(this)
i18nplugin.i18n("key") availableNot available — use plain strings
Config entitiesFull support (@ConfigEntity)Not available — use Bukkit getConfig()
Data storageplugin.getDataOperator(Class)UltiToolsAPI.getDataOperator(plugin, Class)
Hot reloadSupported via ul reloadNot supported — requires server restart
plugin.ymlapi-version: 620depend: [UltiTools]

Complete Working Example

Check out the UltiTools-External-Example repository for a fully working example that demonstrates @Service, @CmdExecutor, @EventListener, @Autowired, and DataOperator CRUD in a standard Bukkit plugin.

Contributors

No contributors

Released under the MIT License.