Build Minecraft plugins the way you'd build a Spring Boot app — annotations, dependency injection, ORM, and scheduled tasks. Works with any Bukkit/Paper plugin. Java 21+, Minecraft 1.21+.
English | 中文文档
Writing Minecraft plugins often means pages of boilerplate — registering commands manually, wiring up listeners, building raw SQL, scheduling BukkitRunnables everywhere. UltiTools-API eliminates all of that.
What you get:
| Feature | Traditional Bukkit | With UltiTools-API |
|---|---|---|
| Commands | onCommand() switch/case |
@CmdMapping(format = "pay <player> <amount>") |
| Listeners | Manual registration in onEnable() |
@EventListener auto-discovered |
| Dependency injection | Static singletons everywhere | @Autowired like Spring |
| Data storage | Raw JDBC or custom file I/O | @Table + @Column ORM, works with MySQL/SQLite/JSON |
| Config | getConfig().getString(...) |
@ConfigEntry on typed fields with validation |
| Scheduled tasks | new BukkitRunnable() boilerplate |
@Scheduled(period = 6000) on any method |
| Feature toggles | Manual if checks |
@ConditionalOnConfig skips entire components |
| Transactions | Manual try/catch/rollback | @Transactional AOP proxy |
| Inter-module events | Custom event buses or static coupling | @ModuleEventHandler pub/sub EventBus |
- Java: 21 or higher
- Minecraft: 1.21+ (Paper)
- UltiTools Plugin: Install on your server
Maven
<dependency>
<groupId>com.ultikits</groupId>
<artifactId>UltiTools-API</artifactId>
<version>6.2.3</version>
</dependency>Gradle
implementation 'com.ultikits:UltiTools-API:6.2.3'Building an UltiTools module is the best way to develop with UltiTools-API. Modules are lightweight plugins managed by the UltiTools framework, giving you everything you'd get from a standalone plugin plus:
- One-click updates — Server admins can update your module directly from the UltiTools panel, no manual JAR swapping
- Built-in marketplace — Publish to UltiCloud and let users discover, install, and manage your module from a central catalog
- Hot reload — Modules can be loaded and unloaded at runtime without restarting the server
- Shared infrastructure — Database connections, config management, and i18n are handled by the framework; your module stays small and focused
- Inter-module communication — Publish and subscribe to events across modules with the built-in EventBus
Quickest start — scaffold with the CLI:
npm install -g @ultikits/cli
ultikits create
# Answer the prompts, then:
cd MyModule && mvn compileThis generates a complete, compilable project with pom.xml, main class, plugin.yml, language files, and tests directory. See the CLI documentation for details.
Or set up manually — create plugin.yml:
name: MyPlugin
version: '${project.version}'
main: com.example.myplugin.MyPlugin
api-version: 620
authors: [ YourName ]Write your main class:
@UltiToolsModule(scanBasePackages = {"com.example.myplugin"})
public class MyPlugin extends UltiToolsPlugin {
@Override
public boolean registerSelf() {
return true; // Commands, listeners, services are all auto-registered!
}
@Override
public void unregisterSelf() { }
}Add @CmdExecutor on command classes, @EventListener on listener classes, and @Service on services. The framework discovers and wires everything automatically.
For detailed guides, see the Developer Documentation or Project Wiki.
Already have a plugin? You can integrate UltiTools-API into any existing Bukkit/Paper plugin without rewriting it as a module. You get the full framework — DI, ORM, scheduled tasks, EventBus — with a single line of code.
Add depend: [UltiTools] to your plugin.yml and call UltiToolsAPI.connect(this):
public class MyBukkitPlugin extends JavaPlugin {
@Override
public void onEnable() {
// One line — UltiTools scans your plugin for @Service, @CmdExecutor, @EventListener, etc.
UltiToolsAPI.connect(this);
}
@Override
public void onDisable() {
UltiToolsAPI.disconnect(this);
}
}After connect(), all annotated classes in your plugin's package are auto-discovered and managed by the UltiTools IoC container — everything a native module gets, while keeping your plugin as a standard Bukkit plugin.
See the External Plugin API Guide for full details, or check out the complete working example.
A complete UltiTools module in under 50 lines:
// Main class — just one annotation to enable auto-scanning
@UltiToolsModule(scanBasePackages = {"com.example.myplugin"})
public class MyPlugin extends UltiToolsPlugin {
@Override
public boolean registerSelf() { return true; }
@Override
public void unregisterSelf() { }
}// Define your data — works with MySQL, SQLite, and JSON automatically
@Data @Builder @NoArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Table("homes")
public class HomeEntity extends BaseDataEntity<String> {
@Column("player_id") private String playerId;
@Column("name") private String name;
@Column("world") private String world;
@Column("x") private double x;
@Column("y") private double y;
@Column("z") private double z;
}// Write commands like controllers — auto-registered, auto-completed
@CmdTarget(CmdTarget.CmdTargetType.PLAYER)
@CmdExecutor(alias = {"home"}, permission = "myplugin.home")
public class HomeCommand extends AbstractCommandExecutor {
@Autowired
private HomeService homeService;
@CmdMapping(format = "tp <name>")
public void teleport(@CmdSender Player player, @CmdParam("name") String name) {
homeService.teleport(player, name);
}
@CmdMapping(format = "set <name>")
public void setHome(@CmdSender Player player, @CmdParam("name") String name) {
homeService.setHome(player, name, player.getLocation());
player.sendMessage("Home set!");
}
@CmdMapping(format = "list")
public void listHomes(@CmdSender Player player) {
homeService.getHomes(player).forEach(h ->
player.sendMessage(" - " + h.getName())
);
}
}// Service with DI, query DSL, and scheduled tasks
@Service
public class HomeService {
@Autowired
private UltiToolsPlugin plugin;
public void teleport(Player player, String name) {
HomeEntity home = plugin.getDataOperator(HomeEntity.class)
.query()
.where("playerId").eq(player.getUniqueId().toString())
.and("name").eq(name)
.first();
if (home != null) {
Location loc = new Location(
Bukkit.getWorld(home.getWorld()),
home.getX(), home.getY(), home.getZ()
);
player.teleport(loc);
}
}
public List<HomeEntity> getHomes(Player player) {
return plugin.getDataOperator(HomeEntity.class)
.query()
.where("playerId").eq(player.getUniqueId().toString())
.orderBy("name")
.list();
}
@Scheduled(period = 72000, async = true) // Cleanup every hour
public void cleanupExpiredHomes() {
// Automatically called by the framework — no BukkitRunnable needed
}
}That's it. No onCommand() switches, no manual listener registration, no new BukkitRunnable() boilerplate, no raw SQL.
Map subcommands directly to methods. Parameters are parsed and type-converted automatically.
@CmdMapping(format = "pay <player> <amount>")
public void pay(@CmdSender Player sender, @CmdParam("player") String target, @CmdParam("amount") double amount) {
// 'amount' is already a double — no manual parsing
}Spring-like dependency injection with three-level cache for circular dependency resolution.
@Service
public class EconomyService {
@Autowired
private TransactionLogger logger; // Auto-injected
@Transactional
public void transfer(UUID from, UUID to, double amount) {
// Automatic rollback on exception
}
}Chain conditions, ordering, and pagination — works across MySQL, SQLite, and JSON storage.
List<AccountEntity> richPlayers = plugin.getDataOperator(AccountEntity.class)
.query()
.where("balance").gte(10000)
.orderByDesc("balance")
.limit(10)
.list();
boolean exists = plugin.getDataOperator(AccountEntity.class)
.query()
.where("owner").eq(uuid)
.and("name").eq("savings")
.exists();Declare scheduled methods — the framework handles BukkitRunnable creation, registration, and cleanup.
@Service
public class InterestService {
@Scheduled(delay = 100, period = 36000, async = true) // 30min cycle, async
public void distributeInterest() {
// Called automatically. Cancelled when plugin unloads.
}
@Scheduled(delay = 20) // One-shot: runs once after 1 second
public void onStartup() {
// Initialization logic
}
}Invalid config values are automatically caught and reset to safe defaults on load and reload.
@Getter @Setter
@ConfigEntity("config/config.yml")
public class EcoConfig extends AbstractConfigEntity {
@ConfigEntry(path = "interestRate", comment = "Interest rate per cycle")
@Range(min = 0.0, max = 1.0)
private double interestRate = 0.0003;
@ConfigEntry(path = "currency_name", comment = "Currency display name")
@NotEmpty
private String currencyName = "Gold Coin";
@ConfigEntry(path = "motd", comment = "Server message of the day")
@Size(min = 1, max = 256)
private String motd = "Welcome!";
}Toggle entire features on/off from config — no if checks scattered through code.
@Service
@ConditionalOnConfig(value = "config/config.yml", path = "enableInterest")
public class InterestService {
// This bean is never created if enableInterest: false
}
@CmdExecutor(alias = {"warp"}, permission = "myplugin.warp")
@ConditionalOnConfig(value = "config/config.yml", path = "enableWarp")
public class WarpCommands extends AbstractCommandExecutor {
// Command doesn't exist if enableWarp: false
}@Transactional and @ExceptionCatch via CGLIB runtime proxies — no boilerplate try/catch.
@Service
public class BankService {
@Transactional(propagation = Propagation.REQUIRED)
public void transfer(UUID from, UUID to, double amount) {
withdraw(from, amount);
deposit(to, amount); // Rolls back both if this throws
}
@ExceptionCatch(value = IOException.class, silent = true)
public String readExternalData() {
// Returns null on IOException instead of crashing
}
}Decoupled pub/sub communication between modules — no direct dependencies needed.
// Define a custom event
public class BalanceChangeEvent extends ModuleEvent {
@Getter private final UUID player;
@Getter private final double amount;
public BalanceChangeEvent(UUID player, double amount) {
this.player = player;
this.amount = amount;
}
}
// Subscribe via annotation — auto-discovered by the framework
@Service
public class AuditService {
@ModuleEventHandler(priority = EventPriority.NORMAL)
public void onBalanceChange(BalanceChangeEvent event) {
log(event.getSourceModule(), event.getPlayer(), event.getAmount());
}
}
// Publish from anywhere
eventBus.publish(new BalanceChangeEvent(player, 100.0));- GUI Framework — Inventory GUIs with pagination via ObliviateInvs, plus a declarative reactive GUI system
- i18n — Built-in internationalization with
i18n("key") - Hot Module Loading — Load/unload plugin modules without server restart
- WebSocket + UltiPanel — Remote server management (commands, files, monitoring, logs)
- Plugin Sandbox — Class blacklisting, path traversal protection, command blocklists
| Document | Description |
|---|---|
| Developer Docs | Full documentation site |
| Project Wiki | Complete project documentation |
| Architecture Guide | System architecture overview |
| IoC Container | Dependency injection guide |
| Command System | Command development guide |
| Data Storage | Database operations guide |
| Quick Start Tutorial | First module tutorial |
| API Reference | API quick reference |
UltiTools-Reborn/
├── src/main/java/com/ultikits/ultitools/
│ ├── UltiTools.java # Main plugin entry
│ ├── api/ # External Plugin API (UltiToolsAPI, ExternalPluginAdapter)
│ ├── abstracts/ # Base classes (UltiToolsPlugin, AbstractCommandExecutor, etc.)
│ ├── annotations/ # Framework annotations (@Service, @CmdExecutor, @Scheduled, etc.)
│ │ └── config/ # Validation annotations (@Range, @NotEmpty, @Size, @Pattern)
│ ├── aop/ # AOP system (CglibProxyFactory, TransactionInterceptor)
│ ├── context/ # IoC container (SimpleContainer, ComponentScanner)
│ ├── events/ # Module EventBus (EventBus, ModuleEvent, EventPriority, Cancellable)
│ ├── manager/ # Core managers (Command, Listener, Config, Plugin, Task)
│ ├── interfaces/ # Core interfaces (DataOperator, Query, DataStore) + impl/
│ ├── websocket/ # UltiPanel WebSocket client + handlers
│ └── utils/ # SecurityPolicy, CloudAuthManager, ApiRateLimiter
├── docs/wiki/ # Project documentation
└── pom.xml
Submit an Issue for bugs and suggestions.
| Contributor | Role |
|---|---|
| @wisdommen | Founder, UltiKits Author |
| @qianmo2233 | Developer, Documentation Maintainer |
| @Shpries | Developer |
| @JueChenChen | Issue Feedback & Suggestions |
| 拾柒 | Graphic Designer |
MIT License — see LICENSE for details.
| Development language | |
| CI/CD | |
| IDE | |
| AI-assisted development | |
| Build tool |
