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
<dependency>
<groupId>com.ultikits</groupId>
<artifactId>UltiTools-API</artifactId>
<version>6.2.5</version>
<scope>provided</scope>
</dependency>2. Declare Dependency in plugin.yml
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
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:
| Annotation | What it does |
|---|---|
@Service | Registered as a bean in the IoC container |
@CmdExecutor | Command class auto-registered with Bukkit |
@EventListener | Listener auto-registered with Bukkit |
@Scheduled | Methods scheduled as Bukkit tasks |
@PlayerCache | Fields tracked per-player with auto save/load |
@ModuleEventHandler | Subscribed to the UltiTools EventBus |
Writing Services
Services work exactly like in UltiTools modules — use @Service and @Autowired:
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:
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
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.
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;
}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:
// 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
| Method | Description |
|---|---|
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 aPluginDisableEventlistener. You don't strictly needdisconnect()inonDisable(), 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
| Feature | UltiTools Module | External Plugin |
|---|---|---|
| Base class | extends UltiToolsPlugin | extends JavaPlugin |
| Registration | @UltiToolsModule + registerSelf() | UltiToolsAPI.connect(this) |
| i18n | plugin.i18n("key") available | Not available — use plain strings |
| Config entities | Full support (@ConfigEntity) | Not available — use Bukkit getConfig() |
| Data storage | plugin.getDataOperator(Class) | UltiToolsAPI.getDataOperator(plugin, Class) |
| Hot reload | Supported via ul reload | Not supported — requires server restart |
| plugin.yml | api-version: 620 | depend: [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.
TIP
See also: IoC Container | Command Executor | Scheduled Tasks | Module EventBus