From c74c80bfe920980673e05cb99a4e5de9f9ebf4c7 Mon Sep 17 00:00:00 2001 From: 1wair <1wairesd.com.industries@gmail.com> Date: Tue, 6 Oct 2026 23:49:50 +0200 Subject: [PATCH] Fix NoClassDefFoundError for CommandProtocol\ on Paper plugin classloader When BukkitScheduler.runTask() dispatches the update-notify Runnable back onto the main thread, CraftTask.run() executes it under the server's classloader rather than ProtocolLib's PaperPluginClassLoader. Anonymous inner classes compiled as Outer\.class entries are not resolvable through the server classloader, causing: java.lang.NoClassDefFoundError: com/comphenix/protocol/CommandProtocol\ The fix follows the pattern already applied in commit dda284cd for SpigotUpdateRunnable: extract the anonymous Runnable from performUpdate() into a named top-level package-private class (UpdateNotifyRunnable) so the runtime always loads a single .class entry through the plugin's own classloader. --- .../comphenix/protocol/CommandProtocol.java | 22 +----- .../protocol/UpdateNotifyRunnable.java | 69 +++++++++++++++++++ 2 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/comphenix/protocol/UpdateNotifyRunnable.java diff --git a/src/main/java/com/comphenix/protocol/CommandProtocol.java b/src/main/java/com/comphenix/protocol/CommandProtocol.java index f782b4399..f38cdd7c4 100644 --- a/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -120,25 +120,9 @@ private void performUpdate(final CommandSender sender, UpdateType type, final bo return; } - // Perform on an async thread - Runnable notify = new Runnable() { - @Override - public void run() { - if (command) { - sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult()); - String remoteVersion = updater.getRemoteVersion(); - if (remoteVersion != null) { - sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion); - sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion()); - } - } else if (updater.shouldNotify() || config.isDebug()) { - sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult()); - } - - updater.removeListener(this); - updateFinished(); - } - }; + // Top-level class instead of anonymous inner class so that PaperPluginClassLoader + // can resolve the .class entry when BukkitScheduler dispatches it on the main thread. + Runnable notify = new UpdateNotifyRunnable(sender, command, updater, config, plugin, this); updater.start(type); updater.addListener(notify); } diff --git a/src/main/java/com/comphenix/protocol/UpdateNotifyRunnable.java b/src/main/java/com/comphenix/protocol/UpdateNotifyRunnable.java new file mode 100644 index 000000000..3abe6a679 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/UpdateNotifyRunnable.java @@ -0,0 +1,69 @@ +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +package com.comphenix.protocol; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.updater.Updater; + +/** + * Callback invoked on the main thread after an asynchronous update check completes. + *

+ * Extracted as a top-level class (instead of an anonymous inner class) so that + * {@code PaperPluginClassLoader} can always resolve its {@code .class} entry when + * {@link org.bukkit.scheduler.BukkitScheduler#runTask} dispatches it back onto the + * main server thread — the same pattern used by {@code SpigotUpdateRunnable}. + */ +final class UpdateNotifyRunnable implements Runnable { + + private final CommandSender sender; + private final boolean command; + private final Updater updater; + private final ProtocolConfig config; + private final Plugin plugin; + private final CommandProtocol owner; + + UpdateNotifyRunnable(CommandSender sender, boolean command, + Updater updater, ProtocolConfig config, + Plugin plugin, CommandProtocol owner) { + this.sender = sender; + this.command = command; + this.updater = updater; + this.config = config; + this.plugin = plugin; + this.owner = owner; + } + + @Override + public void run() { + if (command) { + sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult()); + String remoteVersion = updater.getRemoteVersion(); + if (remoteVersion != null) { + sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion); + sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion()); + } + } else if (updater.shouldNotify() || config.isDebug()) { + sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult()); + } + + updater.removeListener(this); + owner.updateFinished(); + } +}