Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/

package com.velocitypowered.api.proxy.config;

import static java.util.Objects.requireNonNull;

import com.velocitypowered.api.proxy.server.ServerInfoForwardingMode;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

/**
* Exposes server configuration information that plugins may use.<br>
*
* <b>What's the forwarding mode?</b><br>
* The server can use a different mode to obtain and forward player info.<br>
* For instance, if you are running a 1.12 (or lower version) server on a velocity proxy with MODERN player info forwarding
* the server doesn't support MODERN forwarding. So you need to set LEGACY forwarding mode for that server
* and velocity will use ONLY FOR THAT SERVER the legacy forwarding mode.<br><br>
* If the forwarding mode is null it means that the server is using the "player-info-forwarding-mode", set in the config.
*
* @param address The address of the backend server.
* @param forwardingMode The forwarding mode of the backend server.
Comment thread
MarcoLvr marked this conversation as resolved.
* @since 3.4.0
* @see ServerInfoForwardingMode
* @see com.velocitypowered.api.proxy.server.ServerInfo#ServerInfo(String, java.net.InetSocketAddress, ServerInfoForwardingMode)
* @apiNote <i><b>TIP:</b> If you need to set this value when creating dynamic servers in your plugins
* you can do that by adding the {@link ServerInfoForwardingMode} value as the last parameter
* while creating a new {@link com.velocitypowered.api.proxy.server.ServerInfo}.</i>
*/
@NullMarked
public record BackendServerConfig(
String address,
@Nullable ServerInfoForwardingMode forwardingMode
) {
public BackendServerConfig {
requireNonNull(address);
}

public BackendServerConfig(final String address) {
this(address, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,23 @@ public interface ProxyConfig {
* does. For a view of all registered servers, see {@link ProxyServer#getAllServers()}.
*
* @return registered servers map
* @deprecated use {@link #getBackendServers()} instead.
*/
@Deprecated(forRemoval = true, since = "3.4.0")
Map<String, String> getServers();
Comment thread
4drian3d marked this conversation as resolved.

/**
* Get a Map of all servers registered in <code>velocity.toml</code>. This method does
* <strong>not</strong> return all the servers currently in memory, although in most cases it
* does. For a view of all registered servers, see {@link ProxyServer#getAllServers()}.
*
* @return registered servers map with, instead of the only address, the Backend Server Object for each
* of them which contains the address of the server and its info forwarding mode.
* @since 3.4.0
* @see com.velocitypowered.api.proxy.server.ServerInfoForwardingMode
*/
Map<String, BackendServerConfig> getBackendServers();

/**
* Get the order of servers that players will be connected to.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ public final class ServerInfo implements Comparable<ServerInfo> {
private final String name;
private final InetSocketAddress address;

@Nullable
private final ServerInfoForwardingMode forwardingMode;

/**
* Creates a new ServerInfo object.
*
* @param name the name for the server
* @param address the address of the server to connect to
* @param forwardingMode the server info forwarding mode, or {@code null} if the mode from the config should be used
* @since 3.4.0
*/
public ServerInfo(String name, InetSocketAddress address, @Nullable ServerInfoForwardingMode forwardingMode) {
this.name = Preconditions.checkNotNull(name, "name");
this.address = Preconditions.checkNotNull(address, "address");
this.forwardingMode = forwardingMode;
}

/**
* Creates a new ServerInfo object.
*
Expand All @@ -30,6 +47,7 @@ public final class ServerInfo implements Comparable<ServerInfo> {
public ServerInfo(String name, InetSocketAddress address) {
this.name = Preconditions.checkNotNull(name, "name");
this.address = Preconditions.checkNotNull(address, "address");
this.forwardingMode = null;
}

/**
Expand All @@ -50,11 +68,23 @@ public InetSocketAddress getAddress() {
return address;
}

/**
* Returns the forwarding mode used by the backend server to communicate with Velocity.
*
* @return the configured forwarding mode for the server, or {@code null}
* if the mode is inherited from the "player-info-forwarding-mode" set in the config
*/
@Nullable
public final ServerInfoForwardingMode getServerInfoForwardingMode() {
return forwardingMode;
}

@Override
public String toString() {
return "ServerInfo{"
+ "name='" + name + '\''
+ ", address=" + address
+ ", forwarding=" + forwardingMode
+ '}';
}

Expand All @@ -63,17 +93,17 @@ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof final ServerInfo that)) {
return false;
}
ServerInfo that = (ServerInfo) o;
return Objects.equals(name, that.name)
&& Objects.equals(address, that.address);
&& Objects.equals(address, that.address)
&& Objects.equals(forwardingMode, that.forwardingMode);
}

@Override
public int hashCode() {
return Objects.hash(name, address);
return Objects.hash(name, address, forwardingMode);
Comment on lines 99 to +106
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to include forwardingMode in equals() and hashCode() is a breaking change in the API. Previously, two ServerInfo objects with the same name and address but different (or null vs non-null) forwardingMode would be considered equal. Now they won't be. This could break existing code that relies on ServerInfo equality for lookups, comparisons, or use in collections. While this change makes logical sense for the new feature, it should be clearly documented as a breaking change or reconsidered to maintain backward compatibility. Consider whether forwardingMode should be part of the identity of a ServerInfo object or just a configuration detail.

Copilot uses AI. Check for mistakes.
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/

package com.velocitypowered.api.proxy.server;

/**
* Supported per-server player info forwarding methods.
*
* @since 3.4.0
*/
public enum ServerInfoForwardingMode {
Comment thread
4drian3d marked this conversation as resolved.
MODERN,
BUNGEEGUARD,
LEGACY,
NONE
}

23 changes: 18 additions & 5 deletions proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.velocitypowered.proxy;

import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.ServerInfoForwardingMode;
import com.velocitypowered.proxy.util.AddressUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
Expand Down Expand Up @@ -105,17 +106,29 @@ private static class ServerInfoConverter implements ValueConverter<ServerInfo> {

@Override
public ServerInfo convert(String s) {
String[] split = s.split(":", 2);
String[] split = s.split(":", 4);
if (split.length < 2) {
throw new ValueConversionException("Invalid server format. Use <name>:<address>");
throw new ValueConversionException("Invalid server format. Use <name>:<host>:[port]:[forwardingmode]");
}
InetSocketAddress address;
ServerInfoForwardingMode mode = null;
try {
address = AddressUtil.parseAddress(split[1]);
if (split.length >= 3) {
address = AddressUtil.parseAddress(split[1] + ":" + split[2]);
} else {
address = AddressUtil.parseAddress(split[1]);
}
} catch (IllegalStateException e) {
throw new ValueConversionException("Invalid hostname for server flag with name: " + split[0]);
}
return new ServerInfo(split[0], address);
if (split.length == 4) {
try {
mode = ServerInfoForwardingMode.valueOf(split[3].toUpperCase());
} catch (IllegalArgumentException e) {
throw new ValueConversionException("Invalid forwarding mode for server flag with name: " + split[0]);
}
}
return new ServerInfo(split[0], address, mode);
}

@Override
Expand All @@ -125,7 +138,7 @@ public Class<? extends ServerInfo> valueType() {

@Override
public String valuePattern() {
return "name>:<address";
return "name>:<host>:[port]:[forwardingmode]";
Comment on lines 109 to +141
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The valuePattern indicates optional components with brackets "[port]:[forwardingmode]", but the conversion logic at line 109 splits on ":" with a limit of 4, expecting exactly 4 parts for the full format. The description says "Use ::[port]:[forwardingmode]" which suggests port and forwardingmode are optional, but the actual parsing logic requires that if you want to specify forwardingmode, you must include all 4 parts (name, host, port, forwardingmode). The pattern and error message should clarify this or the parsing should be made more flexible to handle name:host:forwardingmode without requiring the port.

Copilot uses AI. Check for mistakes.
}
}
}
17 changes: 13 additions & 4 deletions proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.config.BackendServerConfig;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
Expand Down Expand Up @@ -296,8 +297,12 @@ void start() {
}

if (!options.isIgnoreConfigServers()) {
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
for (Map.Entry<String, BackendServerConfig> entry : configuration.getBackendServers().entrySet()) {
servers.register(new ServerInfo(
entry.getKey(),
AddressUtil.parseAddress(entry.getValue().address()),
entry.getValue().forwardingMode())
);
}
}

Expand Down Expand Up @@ -488,8 +493,12 @@ public boolean reloadConfiguration() throws IOException {
// Re-register servers. If a server is being replaced, make sure to note what players need to
// move back to a fallback server.
Collection<ConnectedPlayer> evacuate = new ArrayList<>();
for (Map.Entry<String, String> entry : newConfiguration.getServers().entrySet()) {
ServerInfo newInfo = new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()));
for (Map.Entry<String, BackendServerConfig> entry : newConfiguration.getBackendServers().entrySet()) {
ServerInfo newInfo = new ServerInfo(
entry.getKey(),
AddressUtil.parseAddress(entry.getValue().address()),
entry.getValue().forwardingMode()
);
Optional<RegisteredServer> rs = servers.getServer(entry.getKey());
if (rs.isEmpty()) {
servers.register(newInfo);
Expand Down
Loading
Loading