This guide helps you resolve common issues when developing with KPaper. If you don't find your issue here, please check our GitHub Issues or create a new one.
Error:
Could not resolve dependency: cc.modlabs:KPaper:LATEST
Causes & Solutions:
-
Missing Repository
// Add to build.gradle.kts repositories { mavenCentral() maven("https://repo-api.modlabs.cc/repo/maven/maven-mirror/") // Add this }
-
Invalid Version
// Use specific version instead of LATEST dependencies { implementation("cc.modlabs:KPaper:2025.1.1.1234") // Use actual version }
-
Network Issues
- Check internet connection
- Try with VPN if corporate firewall blocks access
- Use
--refresh-dependenciesflag:./gradlew build --refresh-dependencies
Error:
Unsupported class file major version 65
Solution: KPaper requires Java 21+. Update your JDK:
// In build.gradle.kts
kotlin {
jvmToolchain(21) // Use Java 21
}
tasks.withType<JavaCompile> {
options.release.set(21)
}Error:
NoSuchMethodError or ClassNotFoundException with Paper classes
Solution: Ensure your Paper version matches KPaper requirements:
dependencies {
paperweight.paperDevBundle("1.21.6-R0.1-SNAPSHOT") // Match KPaper's target version
implementation("cc.modlabs:KPaper:LATEST")
}Error:
Could not load 'plugins/YourPlugin.jar' in folder 'plugins'
Troubleshooting Steps:
-
Check plugin.yml
name: YourPlugin version: '1.0.0' main: com.yourpackage.YourPlugin # Must match your class path api-version: '1.21'
-
Verify Main Class
// Must extend KPlugin, not JavaPlugin class YourPlugin : KPlugin() { override fun startup() { // Implementation } }
-
Check Dependencies
# In plugin.yml - if you depend on other plugins depend: - SomeRequiredPlugin softdepend: - SomeOptionalPlugin
-
Examine Server Logs Look for specific error messages in server console.
Error:
The main instance has been modified, even though it has already been set by another plugin!
Cause: Multiple KPaper plugins creating conflicting instances.
Solution:
// Ensure only one plugin extends KPlugin, or use proper isolation
class YourPlugin : KPlugin() {
override fun startup() {
// Your plugin code
}
}Problem: Event listeners not being called
Troubleshooting:
-
Check Event Registration
// Correct listen<PlayerJoinEvent> { event -> // Handler code } // Wrong - missing listen call { event: PlayerJoinEvent -> // This won't work }
-
Verify Event Priority
// If other plugins cancel events, use higher priority listen<PlayerInteractEvent>(priority = EventPriority.HIGH) { event -> // This runs before NORMAL priority listeners }
-
Check Event Cancellation
listen<BlockBreakEvent> { event -> if (event.isCancelled) { // Event was cancelled by another plugin return@listen } // Your logic here }
Problem: Custom events not being received by listeners
Solution:
// Ensure your custom event extends KEvent properly
class MyCustomEvent(val player: Player) : KEvent() {
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList = HANDLERS
}
override fun getHandlers(): HandlerList = HANDLERS
}
// Dispatch the event properly
fun triggerCustomEvent(player: Player) {
val event = MyCustomEvent(player)
event.callEvent() // Don't forget this
}
// Listen for the event
listen<MyCustomEvent> { event ->
// Handle custom event
}Problem: Commands don't appear in-game or in tab completion
Troubleshooting:
-
Ensure .register() is Called
CommandBuilder("mycommand") .description("My command") .execute { sender, args -> sender.sendMessage("Hello!") } .register() // Must call this!
-
Check Command Name Conflicts
// If another plugin uses the same command name CommandBuilder("mycommand") .aliases("myalias", "mycmd") // Use aliases instead .register()
-
Verify Permission Setup
CommandBuilder("admin") .permission("myplugin.admin") // Make sure this permission exists .execute { sender, args -> // Command logic } .register()
Problem: Arguments not being parsed correctly
Solutions:
-
Check Argument Order
CommandBuilder("teleport") .argument(playerArgument("target")) // Required arguments first .argument(worldArgument("world", optional = true)) // Optional arguments last .execute { sender, args -> val target = args.getPlayer("target") val world = args.getWorldOrDefault("world", sender.world) } .register()
-
Handle Missing Arguments
CommandBuilder("give") .argument(materialArgument("item")) .argument(intArgument("amount", optional = true)) .execute { sender, args -> val material = args.getMaterial("item") val amount = args.getIntOrDefault("amount", 1) // Use default if not provided } .register()
-
Validate Argument Values
CommandBuilder("damage") .argument(intArgument("amount", min = 1, max = 100)) // Add constraints .execute { sender, args -> val amount = args.getInt("amount") // Will be between 1-100 } .register()
Problem: Inventory GUI doesn't open when expected
Troubleshooting:
-
Check Player State
fun openGUI(player: Player) { if (!player.isOnline) return // Player must be online // Close existing inventory first player.closeInventory() val gui = simpleGUI("My GUI", 27) { // GUI content } player.openInventory(gui) }
-
Verify GUI Size
// Size must be multiple of 9, max 54 val gui = simpleGUI("My GUI", 27) { // Valid: 9, 18, 27, 36, 45, 54 // Content }
-
Check for Errors in GUI Building
val gui = simpleGUI("My GUI", 27) { try { item(0, ItemBuilder(Material.STONE).name("Test").build()) { // Click handler } } catch (e: Exception) { plugin.logger.log(java.util.logging.Level.SEVERE, "Error building GUI", e) } }
Problem: GUI click handlers not responding
Solutions:
-
Ensure Click Handler is Set
val gui = simpleGUI("My GUI", 27) { item(0, ItemBuilder(Material.STONE).name("Click me").build()) { // This block is the click handler - don't leave empty player.sendMessage("Clicked!") } }
-
Check for Event Cancellation
// If you have global inventory click handlers, they might interfere listen<InventoryClickEvent> { event -> if (event.view.title.equals("My GUI", ignoreCase = true)) { // Don't cancel KPaper GUI events return@listen } event.isCancelled = true }
-
Verify Slot Numbers
val gui = simpleGUI("My GUI", 27) { // Slots are 0-indexed: 0-26 for size 27 item(26, item) { /* handler */ } // Last slot in 27-slot GUI // item(27, item) { } // This would be invalid! }
Problem: Plugin consuming excessive memory
Solutions:
-
Clean Up Event Listeners
class MyFeature : EventHandler() { override fun load() { listen<PlayerJoinEvent> { /* handler */ } } override fun unload() { // Proper cleanup - KPaper handles this automatically // but you can add custom cleanup here } }
-
Avoid Memory Leaks in GUIs
// Don't store player references in static collections class MyGUI { companion object { private val playerData = mutableMapOf<UUID, PlayerData>() // Bad - can leak } } // Instead, use plugin-scoped collections class MyPlugin : KPlugin() { private val playerData = mutableMapOf<UUID, PlayerData>() // Good override fun shutdown() { playerData.clear() // Clean up on shutdown } }
-
Optimize Data Caching
class DataCache<K, V>( private val maxSize: Int = 1000, private val expireAfter: Duration = Duration.ofMinutes(30) ) { private val cache = LinkedHashMap<K, CacheEntry<V>>() fun get(key: K, loader: () -> V): V { // Implement LRU cache with expiration cleanupExpired() val entry = cache[key] if (entry != null && !entry.isExpired()) { return entry.value } val value = loader() cache[key] = CacheEntry(value, System.currentTimeMillis()) // Evict old entries while (cache.size > maxSize) { cache.remove(cache.keys.first()) } return value } }
Problem: GUIs take long time to open or update
Solutions:
-
Use Async Loading
fun openShop(player: Player) { // Show loading GUI first val loadingGUI = simpleGUI("Loading...", 9) { item(4, ItemBuilder(Material.CLOCK).name("&eLoading...").build()) } player.openInventory(loadingGUI) // Load data asynchronously launch { val items = loadShopItems() // Heavy operation withContext(Dispatchers.Main) { // Update GUI on main thread openShopWithItems(player, items) } } }
-
Cache GUI Elements
object GUICache { private val itemCache = mutableMapOf<String, ItemStack>() fun getCachedItem(key: String, builder: () -> ItemStack): ItemStack { return itemCache.computeIfAbsent(key) { builder() } } } // Usage val item = GUICache.getCachedItem("shop_sword") { ItemBuilder(Material.DIAMOND_SWORD) .name("&cWeapons") .lore("&7Click to browse weapons!") .build() }
Problem: Configuration values returning defaults
Solutions:
-
Check File Location
override fun startup() { // Ensure config file exists if (!File(dataFolder, "config.yml").exists()) { saveDefaultConfig() // Create default config } val config = loadConfiguration<MyConfig>() }
-
Verify Configuration Class
// Ensure your config class has proper defaults data class MyConfig( val database: DatabaseConfig = DatabaseConfig(), val messages: Messages = Messages() ) { data class DatabaseConfig( val host: String = "localhost", val port: Int = 3306, val database: String = "minecraft" ) }
-
Handle Configuration Errors
override fun startup() { try { val config = loadConfiguration<MyConfig>() // Use config } catch (e: Exception) { cc.modlabs.kpaper.main.PluginInstance.logger.log(java.util.logging.Level.SEVERE, "Failed to load configuration", e) // Use defaults or disable plugin server.pluginManager.disablePlugin(this) return } }
Problem: Database operations causing server lag or errors
Solutions:
-
Use Proper Coroutine Context
class DatabaseManager { suspend fun savePlayerData(data: PlayerData) { withContext(Dispatchers.IO) { // Use IO dispatcher for database operations // Database save operation database.save(data) } } fun savePlayerDataAsync(data: PlayerData) { launch { // This uses the plugin's coroutine scope savePlayerData(data) } } }
-
Handle Database Connection Errors
class DatabaseManager { private var isConnected = false suspend fun ensureConnection() { if (!isConnected) { try { database.connect() isConnected = true } catch (e: SQLException) { cc.modlabs.kpaper.main.PluginInstance.logger.log(java.util.logging.Level.SEVERE, "Failed to connect to database", e) throw e } } } suspend fun saveData(data: Any) { try { ensureConnection() database.save(data) } catch (e: SQLException) { cc.modlabs.kpaper.main.PluginInstance.logger.log(java.util.logging.Level.SEVERE, "Failed to save data", e) // Implement retry logic or fallback } } }
Cause: Another instance of the server is already running
Solution: Stop the existing server or use a different port
Cause: Command name conflict with another plugin
Solution: Use aliases or different command names
CommandBuilder("mycommand")
.aliases("mycmd", "mc") // Use aliases instead of conflicting name
.register()Cause: Trying to access block state on wrong block type
Solution: Check block type before accessing state
val block = location.block
if (block.type == Material.CHEST) {
val chest = block.state as Chest
// Safe to use chest
}Cause: Trying to register events after plugin shutdown
Solution: Check plugin state before registering events
override fun startup() {
if (!isEnabled) return // Don't register if plugin is disabled
listen<PlayerJoinEvent> { /* handler */ }
}Enable debug logging to get more information:
class MyPlugin : KPlugin() {
override fun startup() {
// Enable debug logging
logger.level = Level.DEBUG
logDebug("Plugin started in debug mode")
}
}When reporting issues, include:
- KPaper Version
- Paper Version
- Java Version
- Full Error Stack Trace
- Minimal Reproducible Example
- Server Log Excerpt
- GitHub Issues: https://github.com/ModLabsCC/KPaper/issues
- Discord: Join the ModLabs Discord server
- Documentation: Check this documentation for examples
Use this template for bug reports:
**KPaper Version:** 2025.1.1.1234
**Paper Version:** 1.21.6-R0.1-SNAPSHOT
**Java Version:** 21.0.1
**Description:**
Brief description of the issue
**Steps to Reproduce:**
1. Step one
2. Step two
3. Expected vs actual behavior
**Code Sample:**
```kotlin
// Minimal code that reproduces the issueError Log:
// Full stack trace here
Remember: Most issues are configuration-related or due to conflicts with other plugins. Always test with minimal setup first!