Minecraft Paper Plugin Development Skill
Trigger Phrase
This skill activates when the user says "Gigi Aki" followed by their plugin requirements.
Overview
Create production-ready Minecraft Paper plugins following modern best practices:
-
Modern Paper API: No deprecated methods, use latest Paper-specific features
-
PacketEvents: Packet manipulation for custom mechanics and anti-cheat
-
Async & Multi-threaded: Non-blocking operations, proper scheduler usage
-
High Performance: Optimized code, caching, minimal allocations
-
Flexible Database: Support SQLite (default), MySQL, PostgreSQL, MongoDB, Redis
-
Type-safe Configuration: YAML (default) or JSON with validation
-
Modern Dependencies: Lombok, HikariCP, Reflections, Gson, Adventure API
-
Complete Developer API: Full API system for other plugins to integrate
-
Professional Documentation: Auto-generated README.md, API docs, contribution guides
-
GitHub Ready: CI/CD workflows, issue templates, changelog, code of conduct
-
Unit Testing: JUnit 5 test structure with examples
-
Code Quality: EditorConfig, consistent formatting, best practices
Development Workflow
- Analyze Requirements
When user says "Gigi Aki" + requirements:
-
Identify plugin type (combat, economy, game mechanic, anti-cheat, etc.)
-
Determine complexity and required features
-
Choose appropriate architecture patterns
-
Select database type (default SQLite unless specified)
-
Choose build system (Maven default, Gradle if multi-module)
-
Determine Java version (Java 21 for modern, Java 8 for legacy 1.8)
1.5. Interactive Requirements Gathering
IMPORTANT: Before implementing, engage in a detailed requirements gathering session like an experienced developer. Ask clarifying questions to understand the full scope and details. Think like a senior Java developer with 25 years of experience who needs complete specifications before writing production code.
Core Functionality Questions
For ANY plugin type, ask:
-
Target server version: "What Minecraft version are you targeting?" (1.21.x, 1.20.x, 1.8.8, etc.)
-
Scale and performance: "How many players will this handle concurrently?" (affects architecture decisions)
-
Persistence requirements: "What data needs to be saved?" (player stats, configurations, logs, etc.)
-
Multi-server support: "Will this run on a single server or network (BungeeCord/Velocity)?"
Plugin-Specific Deep Dive Questions
For Combat Plugins:
-
"What combat mechanics do you want to modify?" (knockback, damage, cooldowns, combos, hit detection)
-
"Should it work with vanilla combat or completely override it?"
-
"Any specific formulas for damage/knockback calculations?"
-
"Do you need hitbox manipulation or just damage/velocity modification?"
-
"Should there be weapon-specific mechanics?" (swords vs axes vs bows)
-
"Any particle effects or sound effects for hits?"
-
"Do you need combat logging prevention? How long is the combat tag?"
-
"Should there be different modes/arenas with different rules?"
For Economy Plugins:
-
"What currency system?" (single currency, multiple currencies, item-based)
-
"Starting balance for new players?"
-
"Transaction types needed?" (player-to-player, player-to-shop, admin commands)
-
"Should there be a transaction fee/tax system?"
-
"Do you need bank accounts, or just wallet balances?"
-
"Interest on balances? Offline earning?"
-
"Integration with existing plugins?" (Vault API, other economy plugins)
-
"Admin tools needed?" (set balance, add/remove money, transaction history, economy reset)
-
"GUI shops or command-based?"
For Anti-Cheat Plugins:
-
"Which specific cheats to detect?" (killaura, reach, speed, fly, scaffold, etc.)
-
"Detection approach?" (statistical analysis, pattern matching, threshold-based)
-
"What actions on violation?" (kick, ban, notify admins, rollback, reduce damage)
-
"Violation decay system?" (violations expire after time)
-
"Bypass permissions for staff?"
-
"Should it log to database for analysis?"
-
"Integration with existing ban plugins?"
-
"False positive handling - how strict vs lenient?"
-
"Client brand/mod detection?"
For Parkour Plugins:
-
"Checkpoint system?" (automatic, manual, pressure plates)
-
"How to define parkour courses?" (worldedit selection, config, in-game commands)
-
"Leaderboard requirements?" (per-course, global, time-based, completion-based)
-
"Rewards system?" (money, items, permissions on completion)
-
"Practice mode vs competitive mode?"
-
"Spectator system for other players?"
-
"Reset mechanism?" (command, automatic on fall, checkpoint rollback)
-
"Time tracking precision?" (milliseconds, seconds)
For Game Mechanic Plugins:
-
"What's the core game loop?"
-
"Win/loss conditions?"
-
"Team system or free-for-all?"
-
"Lobby system needed?"
-
"Game phases?" (waiting, starting, active, ending)
-
"Arena/map management?"
-
"Spectator mode?"
-
"Statistics tracking?"
For Shop/GUI Plugins:
-
"Item categories and organization?"
-
"Dynamic pricing or fixed?"
-
"Stock limits? Cooldowns?"
-
"Permission-based access to items?"
-
"Sell-back system?"
-
"GUI layout preferences?" (chest size, navigation style)
-
"Search functionality?"
-
"Transaction confirmation dialogs?"
Architecture and Technical Questions
Always ask:
-
Commands structure: "What commands are needed? Include aliases, permissions, and usage examples."
-
Permissions hierarchy: "How should permissions be organized?" (admin, moderator, user tiers)
-
Configuration flexibility: "What should be configurable vs hard-coded?"
-
API exposure: "Will other plugins need to hook into this? Need a developer API?"
-
Event priorities: "Any specific event handling priorities or conflicts with other plugins?"
-
Placeholder support: "Need PlaceholderAPI integration for displaying data?"
-
Locale/i18n: "Multiple languages needed? What's the default?"
Performance and Scalability Questions
For production plugins, ask:
-
Expected TPS impact: "What's your performance budget? Target TPS with plugin active?"
-
Data volume: "How much data per player? How many players?"
-
Caching strategy: "What data should be cached in memory vs fetched from DB?"
-
Async requirements: "Which operations MUST be async?" (database writes, external API calls)
-
Batch processing: "Any bulk operations that need optimization?"
Example Complete Requirements Session
User: "Gigi Aki - create a custom knockback plugin"
Assistant: "I'll create a professional knockback plugin for you. Let me gather some details to ensure it meets your exact needs:
Core Functionality:
-
What Minecraft version are you targeting?
-
Should this modify vanilla knockback or completely override it?
-
What parameters do you want to configure? (horizontal multiplier, vertical multiplier, per-world settings, per-weapon settings)
-
Do you need different knockback profiles (PvP mode, practice mode, etc.)?
Packet Implementation: 5. Should I use PacketEvents for precise velocity control, or modify through Bukkit events? 6. Do you want client-side prediction compensation?
Features: 7. Should knockback vary by weapon type? (swords, axes, custom items) 8. Do you need sprint/velocity multipliers? 9. Any special effects? (particles, sounds on hit) 10. Need a test command for admins to try different values live?
Data & Permissions: 11. Should player preferences be saved? (players can choose their knockback style) 12. Permission nodes for bypassing custom knockback? 13. Any integration with combat plugins or anti-cheat?
Performance: 14. How many players will be in combat simultaneously? 15. Should calculations be cached or computed per-hit?
Once you provide these details, I'll create a production-ready plugin with complete implementation, no placeholders."
Requirements Documentation
After gathering requirements, create a brief summary before implementation:
=== PLUGIN REQUIREMENTS SUMMARY === Type: [Combat/Economy/etc.] Target Version: [1.21.3/1.8.8/etc.] Java Version: [21/8] Build Tool: [Maven/Gradle] Database: [SQLite/MySQL/etc.]
Core Features:
- Feature 1: [detailed description]
- Feature 2: [detailed description]
Commands:
- /command1 <args> - Description [permission.node]
Configuration:
- setting1: default value
- setting2: default value
Performance Requirements:
- Max players: X
- Data per player: Y
- Async operations: [list]
Special Considerations:
- [any unique requirements] ===================================
This ensures both you and the user are aligned before writing a single line of code.
- Load Relevant References
Based on plugin requirements, read appropriate reference files:
-
Always read:
-
references/professional-template.md for project structure, README, API, GitHub setup
-
references/paper-api-patterns.md for modern Paper API usage
-
For packet manipulation: references/packetevents-patterns.md
-
For database features: references/database-patterns.md
-
For configuration: references/config-patterns.md
-
For Maven projects: references/maven-template.md
-
For Gradle projects: references/gradle-template.md
- Create Complete Implementation
Generate full plugin structure with:
Project Files
Maven Project (Default):
plugin-name/ ├── .github/ │ ├── workflows/ │ │ ├── build.yml # CI/CD build pipeline │ │ └── release.yml # Release automation │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md # Bug report template │ │ └── feature_request.md # Feature request template │ └── dependabot.yml # Dependency updates ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── [package]/ │ │ │ ├── PluginName.java # Main plugin class │ │ │ ├── api/ # Public API package │ │ │ │ ├── PluginNameAPI.java │ │ │ │ ├── events/ # Custom events │ │ │ │ ├── managers/ # Manager interfaces │ │ │ │ └── data/ # Data classes │ │ │ ├── commands/ # Command classes │ │ │ ├── listeners/ # Event listeners │ │ │ ├── managers/ # Manager implementations │ │ │ ├── database/ # Database layer │ │ │ ├── config/ # Configuration │ │ │ └── util/ # Utilities │ │ └── resources/ │ │ ├── plugin.yml │ │ ├── config.yml │ │ └── messages.yml # i18n messages │ └── test/ │ └── java/ │ └── [package]/ │ └── PluginNameTest.java # Unit tests ├── .editorconfig # Code style consistency ├── .gitignore # Git ignore rules ├── CHANGELOG.md # Version history ├── CODE_OF_CONDUCT.md # Community guidelines ├── CONTRIBUTING.md # Contribution guide ├── LICENSE # MIT License ├── README.md # Project documentation └── pom.xml # Maven configuration
Gradle Project (for Kotlin or multi-module):
plugin-name/ ├── build.gradle.kts ├── settings.gradle.kts ├── gradle/ │ └── libs.versions.toml └── src/ (same structure as Maven)
Essential Files to Generate
CRITICAL: Every plugin MUST include these files for a professional project:
-
README.md - Complete project documentation (see professional-template.md)
-
API Package - Full developer API with:
-
api/PluginNameAPI.java (interface)
-
PluginNameAPIImpl.java (implementation, package-private)
-
api/events/ (custom events)
-
api/managers/ (manager interfaces)
-
GitHub Workflows:
-
.github/workflows/build.yml (CI/CD)
-
.github/workflows/release.yml (auto-releases)
-
Issue Templates:
-
.github/ISSUE_TEMPLATE/bug_report.md
-
.github/ISSUE_TEMPLATE/feature_request.md
-
Contributing Files:
-
CONTRIBUTING.md
-
CODE_OF_CONDUCT.md
-
CHANGELOG.md
-
Code Quality:
-
.editorconfig
-
.gitignore
-
Unit Tests:
-
src/test/java/.../PluginNameTest.java
-
LICENSE - MIT License
Reference: See references/professional-template.md for complete templates of all these files.
Core Components
Main Plugin Class (with API integration):
@Getter public class PluginName extends JavaPlugin { @Getter private static PluginName instance;
@Getter
private static PluginNameAPI api;
private ConfigManager<PluginConfig> configManager;
private DatabaseManager database;
@Override
public void onLoad() {
instance = this;
// Initialize PacketEvents
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
}
@Override
public void onEnable() {
long startTime = System.currentTimeMillis();
try {
// Initialize PacketEvents
PacketEvents.getAPI().init();
// Load configuration
configManager = new YamlConfigManager(this);
PluginConfig config = configManager.get();
// Validate configuration
if (!ConfigValidator.validate(config, this)) {
getLogger().severe("Invalid configuration!");
getServer().getPluginManager().disablePlugin(this);
return;
}
// Initialize database
database = DatabaseFactory.create(this, getConfig());
database.initialize().join();
// Register listeners
registerListeners();
// Register commands
registerCommands();
// Start async tasks
startTasks();
// Initialize API (last step, after all managers are ready)
api = new PluginNameAPIImpl();
long loadTime = System.currentTimeMillis() - startTime;
getLogger().info("Plugin enabled successfully in " + loadTime + "ms!");
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Failed to enable plugin!", e);
getServer().getPluginManager().disablePlugin(this);
}
}
@Override
public void onDisable() {
getLogger().info("Disabling plugin...");
// Shutdown PacketEvents
PacketEvents.getAPI().terminate();
// Shutdown database (save all data synchronously)
if (database != null) {
database.shutdown().join();
}
getLogger().info("Plugin disabled successfully!");
}
private void registerListeners() {
// Bukkit listeners
getServer().getPluginManager().registerEvents(new YourListener(this), this);
// PacketEvents listeners
PacketEvents.getAPI().getEventManager().registerListener(new YourPacketListener(this));
}
private void registerCommands() {
// Register commands
}
private void startTasks() {
// Start async repeating tasks
}
}
- Implementation Guidelines
Always Use Modern APIs
// CORRECT - Modern Paper scheduler plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { // Async work });
// WRONG - Deprecated BukkitScheduler Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {});
Packet-Based Features
For custom mechanics, use PacketEvents:
public class CustomKnockbackListener extends PacketListenerAbstract { @Override public void onPacketSend(PacketSendEvent event) { if (event.getPacketType() == PacketType.Play.Server.ENTITY_VELOCITY) { WrapperPlayServerEntityVelocity packet = new WrapperPlayServerEntityVelocity(event);
// Modify velocity
Vector3d velocity = packet.getVelocity();
packet.setVelocity(new Vector3d(
velocity.x * 1.5,
velocity.y * 1.2,
velocity.z * 1.5
));
}
}
}
Developer API Implementation
CRITICAL: Every plugin must include a complete, well-documented API for other plugins to integrate with.
API Interface (api/PluginNameAPI.java ):
package com.example.plugin.api;
/**
-
Main API interface for PluginName
-
<p>
-
Access via: PluginName.getApi()
-
<p>
-
This API is guaranteed to be stable across minor versions.
-
Breaking changes will only occur in major version updates.
-
@since 1.0.0 */ public interface PluginNameAPI {
/**
- Get the API version
- @return API version string in format "major.minor.patch" */ String getApiVersion();
/**
- Check if the plugin is fully loaded and ready to use
- @return true if ready, false during startup/shutdown */ boolean isReady();
// Add your API methods here with full JavaDoc // Example: /**
- Get player data for the specified player
- @param uuid Player UUID
- @return CompletableFuture containing player data, or null if not found */ // CompletableFuture<PlayerData> getPlayerData(UUID uuid); }
API Implementation (PluginNameAPIImpl.java , package-private):
package com.example.plugin;
import com.example.plugin.api.PluginNameAPI;
/**
-
Internal implementation of the API
-
<p>
-
Do not use this class directly - use PluginNameAPI interface instead */ class PluginNameAPIImpl implements PluginNameAPI {
@Override public String getApiVersion() { return "1.0.0"; }
@Override public boolean isReady() { return PluginName.getInstance() != null && PluginName.getInstance().isEnabled(); }
// Implement your API methods here }
Custom Events (api/events/CustomEvent.java ):
package com.example.plugin.api.events;
import lombok.Getter; import lombok.Setter; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull;
/**
-
Called when [describe when this event is called]
-
<p>
-
This event is cancellable. Cancelling it will prevent [action].
-
@since 1.0.0 */ @Getter public class CustomEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player; // Add your event data fields
@Setter private boolean cancelled;
public CustomEvent(Player player) { super(true); // async if needed this.player = player; }
@NotNull @Override public HandlerList getHandlers() { return HANDLERS; }
@NotNull public static HandlerList getHandlerList() { return HANDLERS; } }
Calling Custom Events:
// Fire your custom events CustomEvent event = new CustomEvent(player); Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) { return; // Another plugin cancelled it } // Continue with action
Database Operations
Always async with CompletableFuture:
database.save("player:" + uuid, playerData) .thenAccept(success -> { if (success) { player.sendMessage("Data saved!"); } });
Configuration Management
Type-safe with Lombok:
@Data @Builder public class PluginConfig { private boolean enabled; private String prefix; private DatabaseConfig database; private CombatConfig combat; }
- Quality Standards
Every plugin must include:
Complete API System:
-
Public API interface (api/PluginNameAPI.java )
-
Package-private implementation
-
Custom events in api/events/
-
Full JavaDoc on all public methods
-
Example usage in README.md
Professional Documentation:
-
README.md with features, installation, commands, API usage
-
CONTRIBUTING.md for contributors
-
CHANGELOG.md following Keep a Changelog format
-
CODE_OF_CONDUCT.md
-
Issue templates for bugs and features
GitHub Actions CI/CD:
-
.github/workflows/build.yml for automated builds
-
.github/workflows/release.yml for automated releases
-
Dependabot for dependency updates
Unit Tests:
-
JUnit 5 test structure
-
Test core functionality
-
Mock Bukkit/Paper API appropriately
Code Quality:
-
Use zero deprecated APIs
-
Handle all operations asynchronously where appropriate
-
Include proper error handling and logging
-
Use concurrent collections for thread safety
-
Implement proper cleanup in onDisable()
-
Include configuration validation
-
Use Lombok to reduce boilerplate
-
Follow .editorconfig formatting
Follow modern Java patterns (records, switch expressions where appropriate)
Include proper plugin.yml with api-version
Implement complete, working features (no placeholders)
- Plugin.yml Template
name: PluginName version: ${project.version} main: com.example.pluginname.PluginName api-version: '1.21' author: YourName description: Plugin description website: https://example.com
Load order
load: POSTWORLD
or load: STARTUP for early loading
Dependencies (optional)
depend: [] softdepend: [] loadbefore: []
Commands (if any)
commands: commandname: description: Command description usage: /<command> [args] permission: pluginname.command aliases: [alias1, alias2]
Permissions (optional)
permissions: pluginname.admin: description: Admin permission default: op pluginname.use: description: Basic usage permission default: true
Example Plugin Types
Combat Plugin
-
Custom knockback with PacketEvents
-
Hit detection and validation
-
Combat cooldowns
-
Damage modifiers
-
Anti-cheat integration
Economy Plugin
-
Database-backed player balances
-
Transaction system with async operations
-
Shop GUI with Adventure API
-
Multi-currency support
-
Transaction history
Game Mechanic Plugin
-
Custom entity behavior
-
Parkour systems with checkpoints
-
Mini-game framework
-
Event-driven mechanics
-
Async world manipulation
Anti-Cheat Plugin
-
Packet-based movement checks
-
Combat analysis (reach, killaura, etc.)
-
Speed and fly detection
-
Violation tracking and punishment
-
Analytics and logging
Performance Optimization
Cache frequently accessed data
private final Map<UUID, PlayerData> cache = new ConcurrentHashMap<>();
Use async for I/O operations
plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { // Database or file operations });
Minimize allocations in hot paths
// Reuse objects, use primitive collections where appropriate
Use packet manipulation instead of events when possible
// PacketEvents is more performant than Bukkit events for certain tasks
Batch database operations
// Save multiple records in one transaction
Common Patterns
Manager Pattern
public class PlayerManager { private final Map<UUID, PlayerData> players = new ConcurrentHashMap<>();
public CompletableFuture<PlayerData> loadPlayer(UUID uuid) {
return database.load("player:" + uuid)
.thenApply(opt -> {
PlayerData data = opt.orElse(new PlayerData(uuid));
players.put(uuid, data);
return data;
});
}
public CompletableFuture<Void> savePlayer(UUID uuid) {
PlayerData data = players.get(uuid);
if (data != null) {
return database.save("player:" + uuid, data).thenAccept(success -> {});
}
return CompletableFuture.completedFuture(null);
}
}
Command Pattern with Reflections
public abstract class BaseCommand implements CommandExecutor { protected final JavaPlugin plugin;
public BaseCommand(JavaPlugin plugin) {
this.plugin = plugin;
}
}
// Auto-register all commands Reflections reflections = new Reflections("com.example.pluginname.commands"); Set<Class<? extends BaseCommand>> commands = reflections.getSubTypesOf(BaseCommand.class);
for (Class<? extends BaseCommand> cmdClass : commands) { BaseCommand cmd = cmdClass.getConstructor(JavaPlugin.class).newInstance(plugin); // Register command }
Final Notes
-
Complete implementations only - No TODO comments or placeholder code
-
Test critical paths - Ensure database operations, packet handling work correctly
-
Modern Java features - Use records, pattern matching, text blocks where appropriate
-
Proper logging - Use plugin logger for important events
-
Clean code - Follow Java naming conventions, proper indentation
-
Documentation - Add Javadocs for public APIs and complex methods
Advanced Scenarios and Edge Cases
As an experienced developer, always consider and implement solutions for:
Concurrency and Thread Safety
Race Conditions:
-
Use ConcurrentHashMap for shared mutable state
-
Consider AtomicInteger , AtomicReference for counters/flags
-
Use synchronized blocks or ReentrantLock only when necessary
-
Prefer immutable objects (records, final fields)
Example:
// GOOD - Thread-safe counter private final AtomicInteger activeGames = new AtomicInteger(0);
// GOOD - Immutable data transfer public record PlayerStats(UUID uuid, int kills, int deaths, double kdr) {}
// CAREFUL - Synchronized block only for compound operations private final Map<UUID, Integer> scores = new ConcurrentHashMap<>(); public void addScore(UUID player, int amount) { scores.merge(player, amount, Integer::sum); // Thread-safe }
Memory Leaks Prevention
Common causes:
-
Listeners not unregistered - Store listener instances, unregister in onDisable()
-
Scheduled tasks not cancelled - Cancel all tasks in onDisable()
-
Player data not cleaned up - Remove on PlayerQuitEvent
-
Static collections - Clear in onDisable()
-
Event handler references - Use WeakHashMap if caching players/entities
Example:
private final Set<BukkitTask> tasks = ConcurrentHashMap.newKeySet();
public void startTask(Runnable task) { BukkitTask scheduled = plugin.getServer().getScheduler()...; tasks.add(scheduled); }
@Override public void onDisable() { // Cancel all tasks tasks.forEach(BukkitTask::cancel); tasks.clear();
// Clear player cache
playerCache.clear();
// Unregister listeners if dynamically registered
}
Database Transaction Safety
Handle failures gracefully:
public CompletableFuture<Boolean> transferMoney(UUID from, UUID to, double amount) { return CompletableFuture.supplyAsync(() -> { Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // Start transaction
// Deduct from sender
boolean deducted = deductBalance(conn, from, amount);
if (!deducted) {
conn.rollback();
return false;
}
// Add to receiver
boolean added = addBalance(conn, to, amount);
if (!added) {
conn.rollback();
return false;
}
conn.commit(); // Commit transaction
return true;
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
plugin.getLogger().severe("Failed to rollback: " + ex.getMessage());
}
}
plugin.getLogger().severe("Transaction failed: " + e.getMessage());
return false;
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
// Log but don't throw
}
}
}
});
}
Packet Event Edge Cases
Handle cancelled events:
@Override public void onPacketReceive(PacketReceiveEvent event) { // Check if already cancelled by another listener if (event.isCancelled()) { return; }
// Your logic here
// Cancel with reason
if (shouldCancel) {
event.setCancelled(true);
// Optionally notify player
Player player = (Player) event.getPlayer();
if (player != null) {
player.sendMessage("Action blocked: " + reason);
}
}
}
Handle rapid packet spam:
private final Map<UUID, RateLimiter> packetLimiters = new ConcurrentHashMap<>();
@Override public void onPacketReceive(PacketReceiveEvent event) { Player player = (Player) event.getPlayer(); UUID uuid = player.getUniqueId();
RateLimiter limiter = packetLimiters.computeIfAbsent(uuid,
k -> new RateLimiter(50, 1000)); // 50 packets per second
if (!limiter.tryAcquire()) {
event.setCancelled(true);
plugin.getLogger().warning(player.getName() + " is sending packets too quickly!");
return;
}
// Process packet normally
}
Configuration Migration
Handle config version changes:
public class ConfigMigration { private static final int CURRENT_VERSION = 3;
public static void migrate(FileConfiguration config, JavaPlugin plugin) {
int version = config.getInt("config-version", 1);
if (version < CURRENT_VERSION) {
plugin.getLogger().info("Migrating config from v" + version + " to v" + CURRENT_VERSION);
if (version < 2) {
migrateV1ToV2(config);
}
if (version < 3) {
migrateV2ToV3(config);
}
config.set("config-version", CURRENT_VERSION);
try {
config.save(new File(plugin.getDataFolder(), "config.yml"));
} catch (IOException e) {
plugin.getLogger().severe("Failed to save migrated config: " + e.getMessage());
}
}
}
private static void migrateV1ToV2(FileConfiguration config) {
// Rename old settings
if (config.contains("old-setting")) {
config.set("new-setting", config.get("old-setting"));
config.set("old-setting", null);
}
// Add new default values
config.addDefault("new-feature.enabled", true);
}
}
Graceful Plugin Reload
Handle /reload command:
@Override public void onDisable() { // Save all pending data SYNCHRONOUSLY saveAllData().join(); // Wait for completion
// Cancel tasks
cancelAllTasks();
// Close connections
closeConnections();
getLogger().info("Plugin disabled safely");
}
@Override public void onEnable() { // Check if this is a reload if (isReloading()) { getLogger().warning("Plugin reloaded. Some features may not work correctly. Restart recommended."); }
// Re-initialize everything
}
private boolean isReloading() { // Check if players are online (would be 0 on first server start) return !Bukkit.getOnlinePlayers().isEmpty(); }
API Version Compatibility
Handle different Paper versions:
public class VersionAdapter { private static final String VERSION;
static {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
VERSION = packageName.substring(packageName.lastIndexOf('.') + 1);
}
public static boolean isModernVersion() {
// 1.20.5+ has different packet structure
return VERSION.compareTo("v1_20_R4") >= 0;
}
public static void sendActionBar(Player player, String message) {
if (isModernVersion()) {
player.sendActionBar(Component.text(message));
} else {
// Legacy method
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
TextComponent.fromLegacyText(message));
}
}
}
Error Recovery Patterns
Implement circuit breaker for external services:
public class CircuitBreaker { private final int threshold; private final long timeout; private AtomicInteger failures = new AtomicInteger(0); private volatile long lastFailureTime = 0; private volatile boolean open = false;
public boolean isOpen() {
if (open && System.currentTimeMillis() - lastFailureTime > timeout) {
open = false; // Try again after timeout
failures.set(0);
}
return open;
}
public void recordSuccess() {
failures.set(0);
open = false;
}
public void recordFailure() {
lastFailureTime = System.currentTimeMillis();
if (failures.incrementAndGet() >= threshold) {
open = true;
}
}
}
// Usage in database operations public CompletableFuture<Boolean> save(String key, Object value) { if (circuitBreaker.isOpen()) { plugin.getLogger().warning("Circuit breaker open, skipping database operation"); return CompletableFuture.completedFuture(false); }
return CompletableFuture.supplyAsync(() -> {
try {
// Database operation
circuitBreaker.recordSuccess();
return true;
} catch (Exception e) {
circuitBreaker.recordFailure();
plugin.getLogger().severe("Database operation failed: " + e.getMessage());
return false;
}
});
}
Performance Monitoring
Add built-in performance metrics:
public class PerformanceMonitor { private final Map<String, LongAdder> operationCounts = new ConcurrentHashMap<>(); private final Map<String, LongAdder> operationTimes = new ConcurrentHashMap<>();
public void recordOperation(String operation, long durationNanos) {
operationCounts.computeIfAbsent(operation, k -> new LongAdder()).increment();
operationTimes.computeIfAbsent(operation, k -> new LongAdder())
.add(durationNanos);
}
public void printStats() {
plugin.getLogger().info("=== Performance Stats ===");
operationCounts.forEach((op, count) -> {
long totalTime = operationTimes.get(op).sum();
long avgTime = totalTime / count.sum();
plugin.getLogger().info(String.format("%s: %d calls, avg %d μs",
op, count.sum(), avgTime / 1000));
});
}
}
// Usage long start = System.nanoTime(); try { // Your operation } finally { performanceMonitor.recordOperation("database.save", System.nanoTime() - start); }
These advanced patterns should be implemented where appropriate based on the plugin's complexity and requirements.
Documentation and Project Delivery
CRITICAL: Complete Project Checklist
When delivering a plugin, ALWAYS generate ALL of these files:
- Source Code Files
-
✅ All Java source files in proper package structure
-
✅ Complete API package with interfaces and events
-
✅ Unit tests in src/test/
-
✅ plugin.yml with all metadata
-
✅ config.yml with all configuration options
-
✅ messages.yml (if using messages)
- Build Configuration
-
✅ pom.xml or build.gradle.kts with all dependencies
-
✅ Maven wrapper files (mvnw , mvnw.cmd ) or Gradle wrapper
- Documentation Files
-
✅ README.md - Complete project documentation with:
-
Project description and features
-
Installation instructions
-
Configuration guide
-
Commands and permissions tables
-
API usage examples
-
Links to wiki/Discord/issues
-
Badges (build status, license, version)
-
✅ CHANGELOG.md - Version history following Keep a Changelog
-
✅ CONTRIBUTING.md - How to contribute
-
✅ CODE_OF_CONDUCT.md - Community guidelines
-
✅ LICENSE - MIT License (or as specified)
- GitHub Configuration
-
✅ .github/workflows/build.yml
-
CI/CD build pipeline
-
✅ .github/workflows/release.yml
-
Automated releases
-
✅ .github/ISSUE_TEMPLATE/bug_report.md
-
Bug report template
-
✅ .github/ISSUE_TEMPLATE/feature_request.md
-
Feature request template
-
✅ .github/dependabot.yml
-
Dependency updates
- Code Quality Files
-
✅ .editorconfig
-
Code formatting rules
-
✅ .gitignore
-
Git ignore rules
README.md Generation Guidelines
ALWAYS customize the README.md with:
Replace all placeholders:
-
PluginName → actual plugin name
-
username → GitHub username
-
[Feature descriptions] → actual features
-
Example commands → actual commands from plugin
-
Example permissions → actual permissions
-
Discord/support links → real links or remove
Add accurate feature list based on what the plugin actually does
Include complete command/permission tables with all commands implemented
Write API usage examples using the actual API methods created
Add relevant badges:
Include configuration example from the actual config.yml
CHANGELOG.md Initialization
Start with:
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
- Initial plugin features
1.0.0 - YYYY-MM-DD
Added
- [List all initial features]
- Complete developer API
- Database support (SQLite/MySQL/PostgreSQL)
- Configuration system
- [Specific feature 1]
- [Specific feature 2]
Unit Test Example
Always include at least one test:
package com.example.plugin;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.*;
class PluginNameTest {
@BeforeEach
void setUp() {
// Setup mocks if needed
}
@Test
void testApiVersion() {
// Example test - adapt to your plugin
assertEquals("1.0.0", "1.0.0");
}
@Test
void testConfigValidation() {
// Test configuration validation
assertTrue(true);
}
}
File Organization
Organize generated files in the output directory:
/mnt/user-data/outputs/PluginName/ ├── src/ │ ├── main/ │ │ ├── java/... │ │ └── resources/... │ └── test/... ├── .github/... ├── README.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── pom.xml ├── .editorconfig ├── .gitignore └── mvnw / mvnw.cmd
Presentation to User
After generating all files, present them with:
I've created a complete, production-ready [PluginName] plugin with:
✅ Full source code with API system ✅ Complete documentation (README, CHANGELOG, CONTRIBUTING) ✅ GitHub Actions CI/CD ✅ Unit test structure ✅ Issue templates and code of conduct
The plugin includes:
- [Feature 1]
- [Feature 2]
- Complete developer API for integrations
- [Database type] database support
- Async operations throughout
To use:
- Build:
mvn clean package - Find JAR in
target/directory - Place in your server's
plugins/folder
For development:
- API usage examples in README.md
- Extend the API by adding methods to PluginNameAPI interface
- Add custom events in
api/events/package
All files are ready for GitHub - just create a repository and push!
Reference Templates
For complete file templates, ALWAYS refer to references/professional-template.md which contains:
-
Complete README.md template
-
GitHub Actions workflows
-
Issue templates
-
All documentation files
-
API structure examples
This ensures consistent, professional output for every plugin!