Skip to main content

Plugin Messaging

First introduced in 2012, Plugin messaging is a way for plugins to communicate with clients. When your servers are behind a proxy, it will allow your plugins to communicate with the proxy server.

BungeeCord channel

The BungeeCord channel is used for communication between your Paper server and a BungeeCord (or a BungeeCord-compatible) proxy.

Originally, the channel supported by the BungeeCord proxy was called BungeeCord. In versions 1.13 and above, the channel was renamed to bungeecord:main to create a key structure for plugin messaging channels.

Paper handles this change internally and automatically changes any messages sent on the BungeeCord channel to the bungeecord:main channel. This means that your plugins should continue to use the BungeeCord channel.

Sending plugin messages

First, we're going to take a look at your Paper server. Your plugin will need to register that it will be sending on any given plugin channel. You should to do this alongside your other event listener registrations.

public final class PluginMessagingSample extends JavaPlugin {

@Override
public void onEnable() {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
// Blah blah blah...
}

}

Now that we're registered, we can send messages on the BungeeCord channel.

Plugin messages are formatted as byte arrays and can be sent using the sendPluginMessage method on a Player object. Let's take a look at an example of sending a plugin message to the BungeeCord channel to send our player to another server.

public final class PluginMessagingSample extends JavaPlugin implements Listener {

@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
}

@EventHandler
public void onPlayerJump(PlayerJumpEvent event) {
Player player = event.getPlayer();

ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF("hub2");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
}

}
tip

These channels rely on the Minecraft protocol, and are sent as a special type of packet called a Plugin Message. They piggyback on player connections, so if there is no player connected to the server, it will not be able to send or receive plugin messages.

What did we just do?

We sent a plugin message on the BungeeCord channel! The message we sent was a byte array that contained two strings converted to bytes: Connect and hub2.

Our proxy server received the message through the player who triggered the PlayerJumpEvent on our Java server. Then, it recognized the channel as its own and, in alignment with BungeeCord's protocol, sent our player to the hub2 server.

For BungeeCord, we can think of this message as a case-sensitive command with arguments. Here, our command is Connect and our only argument is hub2, but some "commands" may have multiple arguments. For other channels introduced by client side mods, refer to their documentation to best understand how to format your messages.

Plugin message types

Although we sent a Connect message to the proxy, there are a few other cases that BungeeCord-compatible proxies will act on. These are the following:

Message TypeDescriptionArgumentsResponse
ConnectConnects the player to the specified server.server nameN/A
ConnectOtherConnects another player to the specified server.player name, server nameN/A
IPReturns the IP of the specified player.player nameIP, port
PlayerCountReturns the number of players on the specified server.server nameserver name, player count
PlayerListReturns a list of players on the specified server.server nameserver name, CSV player names
GetServersReturns a list of all servers.N/ACSV server names
MessageSends a message to the specified player.player name, messageN/A
MessageRawSends a raw chat component to the specified player.player name, JSON chat componentN/A
GetServerReturns the server the player is connected to.N/Aserver name
UUIDReturns the UUID of player.N/AUUID
UUIDOtherReturns the UUID of the specified player.player nameplayer name, UUID
ServerIpReturns the IP of the specified server.server nameserver name, IP, port
KickPlayerKicks the specified player.player name, reasonN/A
ForwardForwards a plugin message to another server.server, subchannel, size of plugin message, messagesubchannel, size of plugin message, message
ForwardToPlayerForwards a plugin message to another player.player name, subchannel, size of plugin message, messagesubchannel, size of plugin message, message

PlayerCount

public class MyPlugin extends JavaPlugin implements PluginMessageListener {

@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);

Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("PlayerCount");
out.writeUTF("lobby");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// The response will be handled in onPluginMessageReceived
}

@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("PlayerCount")) {
// This is our response to the PlayerCount request
String server = in.readUTF();
int playerCount = in.readInt();
}
}
}

Forward

public class MyPlugin extends JavaPlugin implements PluginMessageListener {

@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);

Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Forward");
out.writeUTF("ALL"); // This is the target server. "ALL" will message all servers apart from the one sending the message
out.writeUTF("SecretInternalChannel"); // This is the channel.

ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
DataOutputStream msgout = new DataOutputStream(msgbytes);
msgout.writeUTF("Paper is the meaning of life"); // You can do anything you want with msgout
msgout.writeShort(42); // Writing a random short

out.writeShort(msgbytes.toByteArray().length); // This is the length.
out.write(msgbytes.toByteArray()); // This is the message.

player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// The response will be handled in onPluginMessageReceived
}

@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("SecretInternalChannel")) {
short len = in.readShort();
byte[] msgbytes = new byte[len];
in.readFully(msgbytes);

DataInputStream msgIn = new DataInputStream(new ByteArrayInputStream(msgbytes));
String secretMessage = msgIn.readUTF(); // Read the data in the same way you wrote it
short meaningofLife = msgIn.readShort();
}
}
}

This message is used to forward a plugin message to another server. This is useful for server-to-server communication within a proxy network. For example, if a certain player is banned on one server, you can forward a message to all other servers to ban them there too.

Example of banning a player on all servers

This is not a recommended way to ban players, due to the fact that there may not be anyone online on the target servers, but it is an example of how this can be used.

MessageRaw

The MessageRaw message type is used to send a raw chat component to a player. The target player is specified by the second parameter - Player name or "ALL" for all players. This is also useful for sending messages to players who are on a different server within the proxied network.

public class MyPlugin extends JavaPlugin {

@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");

Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("MessageRaw");
out.writeUTF("ALL");
out.writeUTF(GsonComponentSerializer.gson().serialize(
Component.text("Click Me!").clickEvent(ClickEvent.openUrl("https://papermc.io"))
));
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
}
}

This will send the player a clickable message saying "Click Me!" that opens https://papermc.io upon clicking.