Skip to content

Plugin messaging

First introduced in 2012, plugin messaging is a way for Velocity plugins to communicate with clients and backend servers.

Velocity manages connections in both directions, for both the client and backend server. This means Velocity plugins need to consider 4 main cases:

flowchart LR
player -->|"1 (Incoming)"| Velocity -->|"2 (Outgoing)"| backend
backend -->|"3 (Incoming)"| Velocity -->|"4 (Outgoing)"| player

Additionally, BungeeCord channel compatibility is included, which may remove the need for a companion Velocity plugin in certain cases.

Case 1: Receiving a plugin message from a player

This is for when you need to handle or inspect the contents of a plugin message sent by a player. It will require registering with the ChannelRegistrar for the event to be fired.

An example use case could be logging messages from a mod that reports the enabled features.

flowchart LR
subgraph handle from player
direction LR
a[player] --> b[Velocity] ~~~ c[backend]
end
subgraph forward from player
direction LR
d[player] --> e[Velocity] --> f[backend]
end
public static final MinecraftChannelIdentifier IDENTIFIER = MinecraftChannelIdentifier.from("custom:main");
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
proxyServer.getChannelRegistrar().register(IDENTIFIER);
}
@Subscribe
public void onPluginMessageFromPlayer(PluginMessageEvent event) {
// Check if the identifier matches first, no matter the source.
if (!IDENTIFIER.equals(event.getIdentifier())) {
return;
}
// mark PluginMessage as handled, indicating that the contents
// should not be forwarding to their original destination.
event.setResult(PluginMessageEvent.ForwardResult.handled());
// Alternatively:
// mark PluginMessage as forwarded, indicating that the contents
// should be passed through, as if Velocity is not present.
//event.setResult(PluginMessageEvent.ForwardResult.forward());
// only attempt parsing the data if the source is a player
if (!(event.getSource() instanceof Player player)) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
// handle packet data
}

Case 2: Sending a plugin message to a backend server

This is for when you need to send a plugin message to a backend server.

There are two methods to send a plugin message to the backend, depending on what you need to achieve.

flowchart LR
subgraph Send to Backend
direction LR
player ~~~ Velocity --> backend
end

Using any connected player

This is useful if you just want to communicate something relevant to the entire server, or otherwise can be derived from its content.

An example use case could be telling the server to shut down.

public boolean sendPluginMessageToBackend(RegisteredServer server, ChannelIdentifier identifier, byte[] data) {
// On success, returns true
return server.sendPluginMessage(identifier, data);
}

Using a specific player’s connection

This is useful if you want to communicate something about a specific player to their current backend server. You may want additional checks to ensure it will be handled correctly on the backend the player is on.

An example use case could be telling the backend server to give the player a specific item.

public boolean sendPluginMessageToBackendUsingPlayer(Player player, ChannelIdentifier identifier, byte[] data) {
Optional<ServerConnection> connection = player.getCurrentServer();
if (connection.isPresent()) {
// On success, returns true
return connection.get().sendPluginMessage(identifier, data);
}
return false;
}

Case 3: Receiving a plugin message from a backend server

This is for when you need to receive plugin messages from your backend server. It will require registering with the ChannelRegistrar for the event to be fired.

An example use case could be handing a request to transfer the player to another server.

flowchart LR
subgraph handle from backend
direction RL
a[backend] --> b[Velocity] ~~~ c[ player]
end
subgraph forward from backend
direction RL
d[backend] --> e[Velocity] --> f[player]
end
public static final MinecraftChannelIdentifier IDENTIFIER = MinecraftChannelIdentifier.from("custom:main");
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
proxyServer.getChannelRegistrar().register(IDENTIFIER);
}
@Subscribe
public void onPluginMessageFromBackend(PluginMessageEvent event) {
// Check if the identifier matches first, no matter the source.
// this allows setting all messages to IDENTIFIER as handled,
// preventing any client-originating messages from being forwarded.
if (!IDENTIFIER.equals(event.getIdentifier())) {
return;
}
// mark PluginMessage as handled, indicating that the contents
// should not be forwarding to their original destination.
event.setResult(PluginMessageEvent.ForwardResult.handled());
// Alternatively:
// mark PluginMessage as forwarded, indicating that the contents
// should be passed through, as if Velocity is not present.
//
// this should be used with extreme caution,
// as any client can freely send whatever it wants, pretending to be the proxy
//event.setResult(PluginMessageEvent.ForwardResult.forward());
// only attempt parsing the data if the source is a backend server
if (!(event.getSource() instanceof ServerConnection backend)) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
// handle packet data
}

Case 4: Sending a plugin message to a player

This is for when you need to send a plugin message to a player.

flowchart RL
subgraph Send to Player
direction RL
backend ~~~ Velocity --> player
end
public boolean sendPluginMessageToPlayer(Player player, ChannelIdentifier identifier, byte[] data) {
// On success, returns true
return player.sendPluginMessage(identifier, data);
}

BungeeCord channel compatibility

This allows your backend servers to communicate with Velocity in a way compatible with BungeeCord.

By default, your Velocity server will respond to the bungeecord:main channel, if bungee-plugin-message-channel is enabled in the configuration.