From 38ff21afd746d9b06f41f6786e25fa8cd4243dda Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Fri, 22 May 2026 16:50:48 +0200 Subject: [PATCH 01/33] Fix theoretical IOOBE race (#1799) --- .../connection/util/ServerListPingHandler.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 12c6be2b..5ccb2b11 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -57,16 +57,16 @@ public class ServerListPingHandler { List samplePlayers; if (configuration.getSamplePlayersInPing()) { List unshuffledPlayers = server.getAllPlayers().stream() - .map(p -> { - if (p.getPlayerSettings().isClientListingAllowed()) { - return new ServerPing.SamplePlayer(p.getUsername(), p.getUniqueId()); - } else { - return ServerPing.SamplePlayer.ANONYMOUS; - } - }) - .collect(Collectors.toList()); + .map(p -> { + if (p.getPlayerSettings().isClientListingAllowed()) { + return new ServerPing.SamplePlayer(p.getUsername(), p.getUniqueId()); + } else { + return ServerPing.SamplePlayer.ANONYMOUS; + } + }) + .collect(Collectors.toList()); Collections.shuffle(unshuffledPlayers); - samplePlayers = unshuffledPlayers.subList(0, Math.min(12, server.getPlayerCount())); + samplePlayers = unshuffledPlayers.subList(0, Math.min(12, unshuffledPlayers.size())); } else { samplePlayers = ImmutableList.of(); } -- 2.39.5 From 7d68208e111e20ba69f27f548e2bc314c2ee4003 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Fri, 22 May 2026 17:25:48 +0200 Subject: [PATCH 02/33] Always close HttpClient (#1798) * Always close HttpClient (even on exception) * Get rid of try/catch since we now use java 21 --- .../connection/client/InitialLoginSessionHandler.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 482fb76e..f6e1cd6e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -268,14 +268,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); } }, mcConnection.eventLoop()) - .thenRun(() -> { - try { - httpClient.close(); - } catch (Exception e) { - // In Java 21, the HttpClient does not throw any Exception - // when trying to clean its resources, so this should not happen - logger.error("An unknown error occurred while trying to close an HttpClient", e); - } + .whenComplete((ignored, throwable) -> { + httpClient.close(); }); } catch (GeneralSecurityException e) { logger.error("Unable to enable encryption", e); -- 2.39.5 From b72cf26802c4087994ea41dbdc3718941d0ac836 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 24 May 2026 17:55:07 +0200 Subject: [PATCH 03/33] Cap pre-join plugin-message queue size (prevents arbitrary growth/OOM) (#1800) * Cap pre-join plugin-message queue size (prevents arbitrary growth/OOM) * Clear counters once as the entire queue will have been processed --- .../client/ClientPlaySessionHandler.java | 52 +++++++++++++++++-- .../proxy/l10n/messages.properties | 1 + 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 821c0987..8880233b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -86,6 +86,8 @@ import java.util.Queue; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; @@ -102,12 +104,23 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private static final boolean BACKPRESSURE_LOG = Boolean.getBoolean("velocity.log-server-backpressure"); + // Caps the per-connection queue used while the FML/login phases are not yet "complete". Without + // these caps, a client that never completes its handshake phase can spam plugin messages (each up + // to ~32 KiB serverbound) and grow the queue without bound. + private static final long MAX_QUEUED_LOGIN_PLUGIN_MESSAGE_BYTES = + Long.getLong("velocity.max-queued-login-plugin-message-bytes", 4L * 1024 * 1024); + private static final int MAX_QUEUED_LOGIN_PLUGIN_MESSAGES = + Integer.getInteger("velocity.max-queued-login-plugin-messages", 1024); + private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class); private final ConnectedPlayer player; private boolean spawned = false; private final List serverBossBars = new ArrayList<>(); private final Queue loginPluginMessages = new ConcurrentLinkedQueue<>(); + private final AtomicLong loginPluginMessagesBytes = new AtomicLong(); + private final AtomicInteger loginPluginMessagesCount = new AtomicInteger(); + private volatile boolean loginPluginMessagesOverflowed; private final VelocityServer server; private @Nullable TabCompleteRequestPacket outstandingTabComplete; private final ChatHandler chatHandler; @@ -176,9 +189,38 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void deactivated() { player.discardChatQueue(); - for (PluginMessagePacket message : loginPluginMessages) { + PluginMessagePacket message; + while ((message = loginPluginMessages.poll()) != null) { ReferenceCountUtil.release(message); } + loginPluginMessagesBytes.set(0); + loginPluginMessagesCount.set(0); + } + + /** + * Adds a retained plugin message to the queue used while the FML/login phases are still in + * progress, enforcing the per-connection byte and count caps. Returns {@code true} if queued, + * {@code false} if the packet was released (and the player disconnected on overflow). + */ + private boolean enqueueLoginPluginMessage(PluginMessagePacket packet) { + if (loginPluginMessagesOverflowed) { + ReferenceCountUtil.release(packet); + return false; + } + int packetSize = packet.content().readableBytes(); + long newBytes = loginPluginMessagesBytes.addAndGet(packetSize); + int newCount = loginPluginMessagesCount.incrementAndGet(); + if (newBytes > MAX_QUEUED_LOGIN_PLUGIN_MESSAGE_BYTES + || newCount > MAX_QUEUED_LOGIN_PLUGIN_MESSAGES) { + loginPluginMessagesOverflowed = true; + ReferenceCountUtil.release(packet); + logger.warn("Disconnecting {}: pre-join plugin-message queue exceeded its limits " + + "({} messages, {} bytes).", player, newCount, newBytes); + player.disconnect(Component.translatable("velocity.error.plugin-message-overflow")); + return false; + } + loginPluginMessages.add(packet); + return true; } @Override @@ -359,7 +401,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // // We also need to make sure to retain these packets, so they can be flushed // appropriately. - loginPluginMessages.add(packet.retain()); + enqueueLoginPluginMessage(packet.retain()); } else { // The connection is ready, send the packet now. backendConn.write(packet.retain()); @@ -374,7 +416,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (!player.getPhase().consideredComplete() || !serverConn.getPhase() .consideredComplete()) { // We're still processing the connection (see above), enqueue the packet for now. - loginPluginMessages.add(message.retain()); + enqueueLoginPluginMessage(message.retain()); } else { backendConn.write(message); } @@ -637,6 +679,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { while ((pm = loginPluginMessages.poll()) != null) { serverMc.delayedWrite(pm); } + loginPluginMessagesBytes.set(0); + loginPluginMessagesCount.set(0); // Clear any title from the previous server. if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { @@ -869,6 +913,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { while ((pm = loginPluginMessages.poll()) != null) { connection.write(pm); } + loginPluginMessagesBytes.set(0); + loginPluginMessagesCount.set(0); } } } diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties index 744cb68b..31f8d1fc 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties @@ -25,6 +25,7 @@ velocity.error.internal-server-connection-error=An internal server connection er velocity.error.logging-in-too-fast=You are logging in too fast, try again later. velocity.error.online-mode-only=You are not logged into your Minecraft account. If you are logged into your Minecraft account, try restarting your Minecraft client. velocity.error.player-connection-error=An internal error occurred in your connection. +velocity.error.plugin-message-overflow=You sent too many plugin messages before completing the connection. velocity.error.modern-forwarding-needs-new-client=This server is only compatible with Minecraft 1.13 and above. velocity.error.modern-forwarding-failed=Your server did not send a forwarding request to the proxy. Make sure the server is configured for Velocity forwarding. velocity.error.moved-to-new-server=You were kicked from : -- 2.39.5 From 25fbd833cd847a76f1e46ce15b70e077e41b72e2 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 24 May 2026 17:59:34 +0200 Subject: [PATCH 04/33] Add decompressed-bytes-per-second rate limit, update packet limiter defaults (#1786) * Add decompressed-bytes-per-second packet limiter, update defaults * Revert "Add compression ratio limiter" --- .../proxy/config/VelocityConfiguration.java | 18 +++--- .../migration/ConfigurationMigration.java | 3 +- .../migration/PacketLimiterMigration.java | 62 +++++++++++++++++++ .../proxy/connection/MinecraftConnection.java | 10 +++ .../network/ServerChannelInitializer.java | 2 +- .../netty/MinecraftCompressDecoder.java | 23 +++++-- .../src/main/resources/default-velocity.toml | 12 +++- 7 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/PacketLimiterMigration.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index e198a071..2c7826e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -31,6 +31,7 @@ import com.velocitypowered.proxy.config.migration.ForwardingMigration; import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration; import com.velocitypowered.proxy.config.migration.MiniMessageTranslationsMigration; import com.velocitypowered.proxy.config.migration.MotdMigration; +import com.velocitypowered.proxy.config.migration.PacketLimiterMigration; import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration; import com.velocitypowered.proxy.util.AddressUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -511,7 +512,8 @@ public class VelocityConfiguration implements ProxyConfig { new KeyAuthenticationMigration(), new MotdMigration(), new MiniMessageTranslationsMigration(), - new TransferIntegrationMigration() + new TransferIntegrationMigration(), + new PacketLimiterMigration() }; for (final ConfigurationMigration migration : migrations) { @@ -1004,12 +1006,13 @@ public class VelocityConfiguration implements ProxyConfig { /** * Configuration for packet limiting. * - * @param interval the interval in seconds to measure packets over - * @param pps the maximum number of packets per second allowed - * @param bytes the maximum number of bytes per second allowed + * @param interval the interval in seconds to measure packets over + * @param pps the maximum number of packets per second allowed + * @param bytes the maximum number of bytes per second allowed + * @param bytesAfterDecompression the maximum number of decompressed bytes per second allowed */ - public record PacketLimiterConfig(int interval, int pps, int bytes) { - public static PacketLimiterConfig DEFAULT = new PacketLimiterConfig(7, 500, -1); + public record PacketLimiterConfig(int interval, int pps, int bytes, int bytesAfterDecompression) { + public static PacketLimiterConfig DEFAULT = new PacketLimiterConfig(7, -1, -1, 5242880); /** * returns a PacketLimiterConfig from a config section, or the default if the section is null. @@ -1022,7 +1025,8 @@ public class VelocityConfiguration implements ProxyConfig { return new PacketLimiterConfig( config.getIntOrElse("interval", DEFAULT.interval()), config.getIntOrElse("packets-per-second", DEFAULT.pps()), - config.getIntOrElse("bytes-per-second", DEFAULT.bytes()) + config.getIntOrElse("bytes-per-second", DEFAULT.bytes()), + config.getIntOrElse("decompressed-bytes-per-second", DEFAULT.bytesAfterDecompression()) ); } else { return DEFAULT; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java index d28578a6..7c00b7bb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java @@ -29,7 +29,8 @@ public sealed interface ConfigurationMigration KeyAuthenticationMigration, MotdMigration, MiniMessageTranslationsMigration, - TransferIntegrationMigration { + TransferIntegrationMigration, + PacketLimiterMigration { boolean shouldMigrate(CommentedFileConfig config); void migrate(CommentedFileConfig config, Logger logger) throws IOException; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PacketLimiterMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PacketLimiterMigration.java new file mode 100644 index 00000000..bbd3ac59 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PacketLimiterMigration.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 Velocity Contributors + * + * 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 3 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, see . + */ + +package com.velocitypowered.proxy.config.migration; + +import static com.velocitypowered.proxy.config.VelocityConfiguration.PacketLimiterConfig.DEFAULT; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import org.apache.logging.log4j.Logger; + +/** + * Configuration migration for the new [packet-limiter] section. + * Config version 2.7 may contain this section with only the `interval`, `packets-per-second` + * and `bytes-per-second` attributes. Config version 2.8 enforces these exist, adds the new + * `decompressed-bytes-per-second` attribute, adjusts the new default, and adds comments. + */ +public final class PacketLimiterMigration implements ConfigurationMigration { + + @Override + public boolean shouldMigrate(CommentedFileConfig config) { + return configVersion(config) < 2.8; + } + + @Override + public void migrate(CommentedFileConfig config, Logger logger) { + config.set("packet-limiter.interval", DEFAULT.interval()); + config.set("packet-limiter.packets-per-second", DEFAULT.pps()); + config.set("packet-limiter.bytes-per-second", DEFAULT.bytes()); + config.set("packet-limiter.decompressed-bytes-per-second", DEFAULT.bytesAfterDecompression()); + + config.setComment("packet-limiter.interval", """ + Size of the moving time window in seconds used to calculate average rates. + A larger window tolerates short bursts while still enforcing the configured limits over time."""); + + config.setComment("packet-limiter.packets-per-second", """ + Maximum average number of packets per second a client may send. -1 disables this check."""); + + config.setComment("packet-limiter.bytes-per-second", """ + Maximum average number of compressed (on-wire) bytes per second a client may send. -1 disables this check."""); + + config.setComment("packet-limiter.decompressed-bytes-per-second", """ + Maximum average number of decompressed bytes per second a client may send. + Protects against compression bomb attacks where small packets expand to excessive sizes after decompression. + -1 disables this check."""); + + config.set("config-version", "2.8"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 0071716d..0cb95752 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -38,7 +38,9 @@ import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler; import com.velocitypowered.proxy.connection.client.StatusSessionHandler; import com.velocitypowered.proxy.network.Connections; +import com.velocitypowered.proxy.network.limiter.SimpleBytesPerSecondLimiter; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.VelocityConnectionEvent; import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder; @@ -571,6 +573,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder); channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder); + var packetLimiterConfig = server.getConfiguration().getPacketLimiterConfig(); + if (minecraftDecoder.getDirection() == ProtocolUtils.Direction.SERVERBOUND + && packetLimiterConfig.interval() > 0 + && packetLimiterConfig.bytesAfterDecompression() > 0) { + decoder.setPacketLimiter(new SimpleBytesPerSecondLimiter( + -1, packetLimiterConfig.bytesAfterDecompression(), packetLimiterConfig.interval())); + } + channel.pipeline().fireUserEventTriggered(VelocityConnectionEvent.COMPRESSION_ENABLED); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java index fae9113f..68c990ea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java @@ -80,7 +80,7 @@ public class ServerChannelInitializer extends ChannelInitializer { int configuredPacketsPerSecond = packetLimiterConfig.pps(); int configuredBytes = packetLimiterConfig.bytes(); - if (configuredInterval > 0 && (configuredBytes > 0 || configuredPacketsPerSecond > 0)) { + if (configuredInterval > 0 && (configuredBytes > 0 || configuredPacketsPerSecond > 0)) { ch.pipeline().get(MinecraftVarintFrameDecoder.class).setPacketLimiter( new SimpleBytesPerSecondLimiter(configuredPacketsPerSecond, configuredBytes, configuredInterval) ); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index 90b747b0..caf2dc77 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -22,11 +22,14 @@ import static com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer; import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame; import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.proxy.network.limiter.PacketLimiter; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; +import org.jspecify.annotations.Nullable; /** * Decompresses a Minecraft packet. @@ -44,11 +47,12 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { Boolean.getBoolean("velocity.increased-compression-cap") ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : SERVERBOUND_MAXIMUM_UNCOMPRESSED_SIZE; private static final boolean SKIP_COMPRESSION_VALIDATION = Boolean.getBoolean("velocity.skip-uncompressed-packet-size-validation"); - private static final double MAX_COMPRESSION_RATIO = Double.parseDouble(System.getProperty("velocity.max-compression-ratio", "64")); private final ProtocolUtils.Direction direction; private int threshold; private final VelocityCompressor compressor; + @Nullable + private PacketLimiter packetLimiter; /** * Creates a new {@code MinecraftCompressDecoder} with the specified compression {@code threshold}. @@ -73,10 +77,13 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { + " threshold %s", actualUncompressedSize, threshold); } // This message is not compressed. + if (packetLimiter != null && !packetLimiter.account(in.readableBytes())) { + throw new QuietDecoderException("Rate limit exceeded while processing packets for %s" + .formatted(ctx.channel().remoteAddress())); + } out.add(in.retain()); return; } - int length = in.readableBytes(); checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than" + " threshold %s", claimedUncompressedSize, threshold); @@ -88,10 +95,6 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { checkFrame(claimedUncompressedSize <= SERVERBOUND_UNCOMPRESSED_CAP, "Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize, SERVERBOUND_UNCOMPRESSED_CAP); - double maxCompressedAllowed = length * MAX_COMPRESSION_RATIO; - checkFrame(claimedUncompressedSize <= maxCompressedAllowed, - "Uncompressed size %s exceeds ratio threshold of %s for compressed sized %s", claimedUncompressedSize, - maxCompressedAllowed, length); } ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize); @@ -99,6 +102,10 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); checkFrame(uncompressed.writerIndex() == claimedUncompressedSize, "Decompressed size %s does not match claimed uncompressed size %s", uncompressed.writerIndex(), claimedUncompressedSize); + if (packetLimiter != null && !packetLimiter.account(claimedUncompressedSize)) { + throw new QuietDecoderException("Rate limit exceeded while processing packets for %s" + .formatted(ctx.channel().remoteAddress())); + } out.add(uncompressed); } catch (Exception e) { uncompressed.release(); @@ -116,4 +123,8 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { public void setThreshold(int threshold) { this.threshold = threshold; } + + public void setPacketLimiter(@Nullable PacketLimiter packetLimiter) { + this.packetLimiter = packetLimiter; + } } diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index 6aa5ceaa..0eae2734 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -1,5 +1,5 @@ # Config version. Do not change this -config-version = "2.7" +config-version = "2.8" # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. bind = "0.0.0.0:25565" @@ -75,9 +75,17 @@ sample-players-in-ping = false enable-player-address-logging = true [packet-limiter] +# Size of the moving time window in seconds used to calculate average rates. +# A larger window tolerates short bursts while still enforcing the configured limits over time. interval = 7 -packets-per-second = 500 +# Maximum average number of packets per second a client may send. -1 disables this check. +packets-per-second = -1 +# Maximum average number of compressed (on-wire) bytes per second a client may send. -1 disables this check. bytes-per-second = -1 +# Maximum average number of decompressed bytes per second a client may send. +# Protects against compression bomb attacks where small packets expand to excessive sizes after decompression. +# -1 disables this check. +decompressed-bytes-per-second = 5242880 [servers] # Configure your servers here. Each key represents the server's name, and the value -- 2.39.5 From 3b142f30998ac3a1a36b1e636d67eae5ea8378b6 Mon Sep 17 00:00:00 2001 From: GoldenEdit <45533337+GoldenEdit@users.noreply.github.com> Date: Thu, 28 May 2026 11:02:48 +0100 Subject: [PATCH 05/33] Fix inverted PluginMessageEvent source/target in InitialConnectSessionHandler (#1811) --- .../proxy/connection/client/InitialConnectSessionHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java index 816d930b..c6e1f0ca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java @@ -65,8 +65,7 @@ public class InitialConnectSessionHandler implements MinecraftSessionHandler { } byte[] copy = ByteBufUtil.getBytes(packet.content()); - PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, - copy); + PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy); server.getEventManager().fire(event) .thenAcceptAsync(pme -> { if (pme.getResult().isAllowed() && serverConn.isActive()) { -- 2.39.5 From 1b64a70a00722e2381b267ec2ebd250fa8d3a589 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Tue, 2 Jun 2026 16:22:33 +0200 Subject: [PATCH 06/33] Fix underflow and overflow error messages (#1812) --- .../proxy/protocol/netty/MinecraftDecoder.java | 2 +- .../proxy/protocol/netty/MinecraftVarintFrameDecoder.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index ce0d3426..4d0062d4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -104,7 +104,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { throw handleOverflow(packet, expectedMaxLen, buf.readableBytes()); } if (buf.readableBytes() < expectedMinLen) { - throw handleUnderflow(packet, expectedMaxLen, buf.readableBytes()); + throw handleUnderflow(packet, expectedMinLen, buf.readableBytes()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 56c58619..e203e4ae 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java @@ -159,10 +159,10 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version); int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version); if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) { - throw handleOverflow(packet, expectedMaxLen, in.readableBytes()); + throw handleOverflow(packet, expectedMaxLen, payloadLength); } if (payloadLength < expectedMinLen) { - throw handleUnderflow(packet, expectedMaxLen, in.readableBytes()); + throw handleUnderflow(packet, expectedMinLen, payloadLength); } in.readerIndex(index); -- 2.39.5 From 3b892899db6bcfc515af54e75b7267d792887dbd Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Mon, 15 Jun 2026 16:47:31 +0200 Subject: [PATCH 07/33] Require non-null `reason` (#1823) --- api/src/main/java/com/velocitypowered/api/proxy/Player.java | 2 +- .../proxy/connection/client/ConnectedPlayer.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 057b8a23..c4b8703f 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -197,7 +197,7 @@ public interface Player extends * * @param reason component with the reason */ - void disconnect(Component reason); + void disconnect(@NotNull Component reason); /** * Sends chat input onto the players current server as if they typed it into the client chat box. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 47d59a71..8d39fcf9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -111,6 +111,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -645,7 +646,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public void disconnect(Component reason) { + public void disconnect(@NotNull Component reason) { + Objects.requireNonNull(reason, "reason"); if (connection.eventLoop().inEventLoop()) { disconnect0(reason, false); } else { -- 2.39.5 From bcf1bba3a14107a517130b5709402e602ff8cc8a Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Mon, 15 Jun 2026 16:48:08 +0200 Subject: [PATCH 08/33] Add missing `package-info.java`s for event packages (#1822) --- .../api/event/command/package-info.java | 11 +++++++++++ .../api/event/player/configuration/package-info.java | 11 +++++++++++ .../api/event/proxy/server/package-info.java | 11 +++++++++++ 3 files changed, 33 insertions(+) create mode 100644 api/src/main/java/com/velocitypowered/api/event/command/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/package-info.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/proxy/server/package-info.java diff --git a/api/src/main/java/com/velocitypowered/api/event/command/package-info.java b/api/src/main/java/com/velocitypowered/api/event/command/package-info.java new file mode 100644 index 00000000..566924f2 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/command/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2018 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. + */ + +/** + * Provides events for handling command execution. + */ +package com.velocitypowered.api.event.command; diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/package-info.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/package-info.java new file mode 100644 index 00000000..b789c711 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2018 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. + */ + +/** + * Provides events for handling the player configuration phase. + */ +package com.velocitypowered.api.event.player.configuration; diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/server/package-info.java b/api/src/main/java/com/velocitypowered/api/event/proxy/server/package-info.java new file mode 100644 index 00000000..c87588ce --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/server/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2018 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. + */ + +/** + * Provides events for handling registration of servers on the proxy. + */ +package com.velocitypowered.api.event.proxy.server; -- 2.39.5 From 0cbe10e95136dd3a67d66c7d634e9f151bc6948c Mon Sep 17 00:00:00 2001 From: Jones <73846784+jonesdevelopment@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:49:05 +0200 Subject: [PATCH 09/33] Bump netty from 4.2.10.Final to 4.2.15.Final (#1817) * Bump netty from 4.2.10.Final to 4.2.14.Final * Bump netty from 4.2.14.Final to 4.2.15.Final Co-authored-by: Wouter Gritter --------- Co-authored-by: Wouter Gritter --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index caa82d81..e00cea72 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ configurate3 = "3.7.3" configurate4 = "4.2.0" flare = "2.0.1" log4j = "2.25.3" -netty = "4.2.10.Final" +netty = "4.2.15.Final" [plugins] fill = "io.papermc.fill.gradle:1.0.10" -- 2.39.5 From d7ad0522e9a7e4874049ffa8ebd40176db61805e Mon Sep 17 00:00:00 2001 From: Aaron <71191102+RealBauHD@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:20:59 +0200 Subject: [PATCH 10/33] Minecraft 26.2 (#1807) * 26.2-snapshot-2 * 26.2-snapshot-3 * 26.2-snapshot-4 * 26.2-pre-1 * 26.2-pre-3 * 26.2-pre-4 * chore: set online mode and session id * fix: checkstyle * 26.2-pre-5 * 26.2-pre-6 * 26.2-rc-1 * 26.2-rc-2 * unregister old color argument in 26.2 * 26.2 --- .../api/network/ProtocolVersion.java | 3 ++- .../backend/TransitionSessionHandler.java | 5 ++++- .../connection/client/AuthSessionHandler.java | 3 +++ .../proxy/protocol/packet/JoinGamePacket.java | 15 ++++++++++++++- .../protocol/packet/ServerLoginSuccessPacket.java | 13 +++++++++++++ .../brigadier/ArgumentPropertyRegistry.java | 4 +++- 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index b166291b..7f6912a6 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -95,7 +95,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_21_7(772, "1.21.7", "1.21.8"), MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"), MINECRAFT_1_21_11(774, "1.21.11"), - MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"); + MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"), + MINECRAFT_26_2(776, "26.2"); private static final int SNAPSHOT_BIT = 30; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java index cfcd8f5e..da8513c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java @@ -89,7 +89,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(JoinGamePacket packet) { - MinecraftConnection smc = serverConn.ensureConnected(); + final MinecraftConnection smc = serverConn.ensureConnected(); final RegisteredServer previousServer = serverConn.getPreviousServer().orElse(null); final ConnectedPlayer player = serverConn.getPlayer(); final VelocityServerConnection existingConnection = player.getConnectedServer(); @@ -106,6 +106,9 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { // Reset Tablist header and footer to prevent desync player.clearPlayerListHeaderAndFooter(); + // Override online mode + packet.setOnlineMode(player.isOnlineMode()); + // The goods are in hand! We got JoinGame. Let's transition completely to the new state. smc.setAutoReading(false); server.getEventManager() diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 605388ea..4d05da9e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -236,6 +236,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler { success.setUsername(player.getUsername()); success.setProperties(player.getGameProfileProperties()); success.setUuid(player.getUniqueId()); + if (inbound.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_26_2)) { + success.setSessionId(UUID.randomUUID()); // use random uuid for now + } mcConnection.write(success); loginState = State.SUCCESS_SENT; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java index 787d858e..645dc7b6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java @@ -52,6 +52,7 @@ public class JoinGamePacket implements MinecraftPacket { private @Nullable Pair lastDeathPosition; // 1.19+ private int portalCooldown; // 1.20+ private int seaLevel; // 1.21.2+ + private boolean onlineMode; // 26.2+ private boolean enforcesSecureChat; // 1.20.5+ public int getEntityId() { @@ -190,6 +191,10 @@ public class JoinGamePacket implements MinecraftPacket { this.seaLevel = seaLevel; } + public void setOnlineMode(boolean onlineMode) { + this.onlineMode = onlineMode; + } + public boolean getEnforcesSecureChat() { return this.enforcesSecureChat; } @@ -213,7 +218,7 @@ public class JoinGamePacket implements MinecraftPacket { dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' + ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance + ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown + - ", seaLevel=" + seaLevel + + ", seaLevel=" + seaLevel + ", onlineMode=" + this.onlineMode + '}'; } @@ -358,6 +363,10 @@ public class JoinGamePacket implements MinecraftPacket { this.seaLevel = ProtocolUtils.readVarInt(buf); } + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) { + this.onlineMode = buf.readBoolean(); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { this.enforcesSecureChat = buf.readBoolean(); } @@ -510,6 +519,10 @@ public class JoinGamePacket implements MinecraftPacket { ProtocolUtils.writeVarInt(buf, seaLevel); } + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) { + buf.writeBoolean(this.onlineMode); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { buf.writeBoolean(this.enforcesSecureChat); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java index 322cd9b1..4b8e722a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java @@ -35,6 +35,7 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { private @Nullable UUID uuid; private @Nullable String username; private @Nullable List properties; + private @Nullable UUID sessionId; private static final boolean strictErrorHandling = VelocityProperties .readBoolean("velocity.strictErrorHandling", true); @@ -68,6 +69,10 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { this.properties = properties; } + public void setSessionId(@Nullable UUID sessionId) { + this.sessionId = sessionId; + } + @Override public String toString() { return "ServerLoginSuccess{" @@ -96,6 +101,10 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { buf.readBoolean(); } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) { + this.sessionId = ProtocolUtils.readUuid(buf); + } } @Override @@ -127,6 +136,10 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { buf.writeBoolean(strictErrorHandling); } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) { + ProtocolUtils.writeUuid(buf, this.sessionId); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 203ab375..3c57cd75 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -24,6 +24,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_2; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -207,7 +208,7 @@ public class ArgumentPropertyRegistry { empty(id("minecraft:block_predicate", mapSet(MINECRAFT_1_19, 13))); empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14))); empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15))); - empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16))); + empty(id("minecraft:color", mapSet(MINECRAFT_26_2, -1), mapSet(MINECRAFT_1_19, 16))); // renamed to team_color in 26.2 empty(id("minecraft:component", mapSet(MINECRAFT_1_21_6, 18), mapSet(MINECRAFT_1_19, 17))); empty(id("minecraft:style", mapSet(MINECRAFT_1_21_6, 19), mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 empty(id("minecraft:message", mapSet(MINECRAFT_1_21_6, 20), mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); @@ -281,6 +282,7 @@ public class ArgumentPropertyRegistry { empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6 empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6 + empty(id("minecraft:team_color", mapSet(MINECRAFT_26_2, 16))); // renamed from color in 26.2 // Crossstitch support register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); -- 2.39.5 From a7581821fb72a3eb5011f725d8876c91aa7843e1 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Tue, 16 Jun 2026 12:26:19 +0100 Subject: [PATCH 11/33] Use a shared per-proxy session ID for 26.2 login metrics The 26.2 login success session ID is purely a metrics identifier. Mint one shared UUID per proxy, regenerated when the proxy empties, mirroring the vanilla server, instead of a random UUID per connection. --- .../velocitypowered/proxy/VelocityServer.java | 32 +++++++++++++++++++ .../connection/client/AuthSessionHandler.java | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 95f10bcb..83e443e9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -165,6 +165,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { private final Map connectionsByUuid = new ConcurrentHashMap<>(); private final Map connectionsByName = new ConcurrentHashMap<>(); + private final Object sessionIdLock = new Object(); + private volatile @Nullable UUID sessionId; private final VelocityConsole console; private @MonotonicNonNull Ratelimiter ipAttemptLimiter; private @MonotonicNonNull Ratelimiter commandRateLimiter; @@ -743,6 +745,36 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection); connectionsByUuid.remove(connection.getUniqueId(), connection); connection.disconnected(); + + if (this.sessionId != null && connectionsByUuid.isEmpty()) { + synchronized (this.sessionIdLock) { + if (connectionsByUuid.isEmpty()) { + this.sessionId = null; + } + } + } + } + + /** + * Returns the metrics session ID for this proxy, generating one if none is currently active. The + * ID is shared by every player connected during a populated period and is regenerated once the + * proxy empties. + * + * @return the current session ID + */ + public UUID getSessionId() { + UUID uuid = this.sessionId; + if (uuid != null) { + return uuid; + } + synchronized (this.sessionIdLock) { + uuid = this.sessionId; + if (uuid == null) { + uuid = UUID.randomUUID(); + this.sessionId = uuid; + } + return uuid; + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 4d05da9e..fee642b4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -237,7 +237,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler { success.setProperties(player.getGameProfileProperties()); success.setUuid(player.getUniqueId()); if (inbound.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_26_2)) { - success.setSessionId(UUID.randomUUID()); // use random uuid for now + success.setSessionId(server.getSessionId()); } mcConnection.write(success); -- 2.39.5 From 1edab1411d3afb95cb6f5c7c8227fc41f1260506 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 5 Jul 2026 17:10:22 +0200 Subject: [PATCH 12/33] Store historical console commands in .console_history (enables command history between restarts) (#1835) --- .../java/com/velocitypowered/proxy/console/VelocityConsole.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index fd7d881d..e6e78eb3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -25,6 +25,7 @@ import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.ClosestLocaleMatcher; +import java.nio.file.Path; import java.util.List; import java.util.Locale; import net.kyori.adventure.audience.MessageType; @@ -111,6 +112,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons protected LineReader buildReader(LineReaderBuilder builder) { return super.buildReader(builder .appName("Velocity") + .variable(LineReader.HISTORY_FILE, Path.of(".console_history")) .completer((reader, parsedLine, list) -> { try { List offers = this.server.getCommandManager() -- 2.39.5 From 81a5817a8270879b8e134e54c3cdef3c83852630 Mon Sep 17 00:00:00 2001 From: Clement Raynaud <30211659+carlodrift@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:27:56 +0200 Subject: [PATCH 13/33] Resolve backend DNS on a bounded thread pool to avoid head-of-line blocking (#1834) * Use a bounded thread pool for backend DNS resolution * Queue DNS lookups instead of rejecting when the resolver pool is busy --- .../netty/SeparatePoolInetNameResolver.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java index 5fc309ae..fe5e8b29 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java @@ -32,17 +32,20 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * An implementation of {@code InetNameResolver} that performs blocking DNS name lookups - * in a separate thread, avoiding blocking the Netty threads for an extended period of time - * and without the downsides of Netty's native DNS resolver. + * on a small bounded pool of separate threads, avoiding blocking the Netty threads for an + * extended period of time and without the downsides of Netty's native DNS resolver. */ public final class SeparatePoolInetNameResolver extends InetNameResolver { + private static final int MAX_RESOLVE_THREADS = 8; + private final ExecutorService resolveExecutor; private final InetNameResolver delegate; private final Cache> cache; @@ -56,11 +59,15 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver { */ public SeparatePoolInetNameResolver(EventExecutor executor) { super(executor); - this.resolveExecutor = Executors.newSingleThreadExecutor( + ThreadPoolExecutor resolveExecutor = new ThreadPoolExecutor( + MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS, + 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryBuilder() - .setNameFormat("Velocity DNS Resolver") + .setNameFormat("Velocity DNS Resolver #%d") .setDaemon(true) .build()); + resolveExecutor.allowCoreThreadTimeOut(true); + this.resolveExecutor = resolveExecutor; this.delegate = new DefaultNameResolver(executor); this.cache = Caffeine.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS) -- 2.39.5 From 97b386d86ff99f787117cb30e8a4b50c83dba6c2 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:53:08 -0500 Subject: [PATCH 14/33] Adventure 5 (#1774) * Adventure 5.2.0 --- api/build.gradle.kts | 4 +--- gradle/libs.versions.toml | 4 ++-- .../connection/client/ConnectedPlayer.java | 23 ++++--------------- .../proxy/console/VelocityConsole.java | 4 +--- .../proxy/protocol/ProtocolUtils.java | 1 - 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 268a8197..0e3304f6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -67,9 +67,7 @@ tasks { "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/", "https://docs.oracle.com/en/java/javase/17/docs/api/", - "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", - "https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/", - "https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/", + "https://jd.papermc.io/adventure/${libs.adventure.bom.get().version}/", "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e00cea72..479b30ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ shadow = "com.gradleup.shadow:9.3.1" spotless = "com.diffplug.spotless:8.2.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.26.1" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.26.1" +adventure-bom = "net.kyori:adventure-bom:5.2.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:5.2.0" adventure-facet = "net.kyori:adventure-platform-facet:4.4.1" asm = "org.ow2.asm:asm:9.9.1" auto-service = "com.google.auto.service:auto-service:1.1.1" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 8d39fcf9..3bf23d5d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -84,7 +84,6 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; -import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; @@ -119,7 +118,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.key.Key; @@ -425,29 +423,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message) { + public void sendMessage(final @NonNull Component message) { + Preconditions.checkNotNull(message, "message"); final Component translated = translateMessage(message); connection.write(getChatBuilderFactory().builder() - .component(translated).forIdentity(identity).toClient()); + .component(translated).toClient()); } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message, - @NonNull MessageType type) { - Preconditions.checkNotNull(message, "message"); - Preconditions.checkNotNull(type, "type"); - - Component translated = translateMessage(message); - - connection.write(getChatBuilderFactory().builder() - .component(translated).forIdentity(identity) - .setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM) - .toClient()); - } - - @Override - public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) { + public void sendActionBar(@NonNull Component message) { Component translated = translateMessage(message); ProtocolVersion playerVersion = getProtocolVersion(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index e6e78eb3..9bb6635f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -28,7 +28,6 @@ import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import java.nio.file.Path; import java.util.List; import java.util.Locale; -import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.platform.facet.FacetPointers; @@ -73,8 +72,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message, - @NonNull MessageType messageType) { + public void sendMessage(@NonNull Component message) { componentLogger.info(message); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 826df5b3..3cb64382 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -60,7 +60,6 @@ public enum ProtocolUtils { private static final GsonComponentSerializer PRE_1_16_SERIALIZER = GsonComponentSerializer.builder() - .downsampleColors() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() -- 2.39.5 From 06eb052ab507dab1ee96787f7363fc34d8adfce3 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 01:33:41 +0100 Subject: [PATCH 15/33] Revert "Adventure 5 (#1774)" This reverts commit 97b386d86ff99f787117cb30e8a4b50c83dba6c2. --- api/build.gradle.kts | 4 +++- gradle/libs.versions.toml | 4 ++-- .../connection/client/ConnectedPlayer.java | 23 +++++++++++++++---- .../proxy/console/VelocityConsole.java | 4 +++- .../proxy/protocol/ProtocolUtils.java | 1 + 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 0e3304f6..268a8197 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -67,7 +67,9 @@ tasks { "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/", "https://docs.oracle.com/en/java/javase/17/docs/api/", - "https://jd.papermc.io/adventure/${libs.adventure.bom.get().version}/", + "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", + "https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/", + "https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/", "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 479b30ce..e00cea72 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ shadow = "com.gradleup.shadow:9.3.1" spotless = "com.diffplug.spotless:8.2.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:5.2.0" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:5.2.0" +adventure-bom = "net.kyori:adventure-bom:4.26.1" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.26.1" adventure-facet = "net.kyori:adventure-platform-facet:4.4.1" asm = "org.ow2.asm:asm:9.9.1" auto-service = "com.google.auto.service:auto-service:1.1.1" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 3bf23d5d..8d39fcf9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -84,6 +84,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; +import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; @@ -118,6 +119,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.key.Key; @@ -423,16 +425,29 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public void sendMessage(final @NonNull Component message) { - Preconditions.checkNotNull(message, "message"); + public void sendMessage(@NonNull Identity identity, @NonNull Component message) { final Component translated = translateMessage(message); connection.write(getChatBuilderFactory().builder() - .component(translated).toClient()); + .component(translated).forIdentity(identity).toClient()); } @Override - public void sendActionBar(@NonNull Component message) { + public void sendMessage(@NonNull Identity identity, @NonNull Component message, + @NonNull MessageType type) { + Preconditions.checkNotNull(message, "message"); + Preconditions.checkNotNull(type, "type"); + + Component translated = translateMessage(message); + + connection.write(getChatBuilderFactory().builder() + .component(translated).forIdentity(identity) + .setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM) + .toClient()); + } + + @Override + public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) { Component translated = translateMessage(message); ProtocolVersion playerVersion = getProtocolVersion(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index 9bb6635f..e6e78eb3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -28,6 +28,7 @@ import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import java.nio.file.Path; import java.util.List; import java.util.Locale; +import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.platform.facet.FacetPointers; @@ -72,7 +73,8 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons } @Override - public void sendMessage(@NonNull Component message) { + public void sendMessage(@NonNull Identity identity, @NonNull Component message, + @NonNull MessageType messageType) { componentLogger.info(message); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 3cb64382..826df5b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -60,6 +60,7 @@ public enum ProtocolUtils { private static final GsonComponentSerializer PRE_1_16_SERIALIZER = GsonComponentSerializer.builder() + .downsampleColors() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() -- 2.39.5 From f11cc86e69bac7d1a9270f21a817f5bc16d4f8f7 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 01:36:06 +0100 Subject: [PATCH 16/33] Welcome to the 3.6.0 series --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d048feb7..a9fde939 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=3.5.0-SNAPSHOT +version=3.6.0-SNAPSHOT -- 2.39.5 From 2b5d964ab014dac0bed6ae6ff7b741b43731bdfd Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 01:36:17 +0100 Subject: [PATCH 17/33] Reapply "Adventure 5 (#1774)" This reverts commit 06eb052ab507dab1ee96787f7363fc34d8adfce3. --- api/build.gradle.kts | 4 +--- gradle/libs.versions.toml | 4 ++-- .../connection/client/ConnectedPlayer.java | 23 ++++--------------- .../proxy/console/VelocityConsole.java | 4 +--- .../proxy/protocol/ProtocolUtils.java | 1 - 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 268a8197..0e3304f6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -67,9 +67,7 @@ tasks { "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/", "https://docs.oracle.com/en/java/javase/17/docs/api/", - "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", - "https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/", - "https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/", + "https://jd.papermc.io/adventure/${libs.adventure.bom.get().version}/", "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e00cea72..479b30ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ shadow = "com.gradleup.shadow:9.3.1" spotless = "com.diffplug.spotless:8.2.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.26.1" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.26.1" +adventure-bom = "net.kyori:adventure-bom:5.2.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:5.2.0" adventure-facet = "net.kyori:adventure-platform-facet:4.4.1" asm = "org.ow2.asm:asm:9.9.1" auto-service = "com.google.auto.service:auto-service:1.1.1" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 8d39fcf9..3bf23d5d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -84,7 +84,6 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; -import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; @@ -119,7 +118,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.key.Key; @@ -425,29 +423,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message) { + public void sendMessage(final @NonNull Component message) { + Preconditions.checkNotNull(message, "message"); final Component translated = translateMessage(message); connection.write(getChatBuilderFactory().builder() - .component(translated).forIdentity(identity).toClient()); + .component(translated).toClient()); } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message, - @NonNull MessageType type) { - Preconditions.checkNotNull(message, "message"); - Preconditions.checkNotNull(type, "type"); - - Component translated = translateMessage(message); - - connection.write(getChatBuilderFactory().builder() - .component(translated).forIdentity(identity) - .setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM) - .toClient()); - } - - @Override - public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) { + public void sendActionBar(@NonNull Component message) { Component translated = translateMessage(message); ProtocolVersion playerVersion = getProtocolVersion(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index e6e78eb3..9bb6635f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -28,7 +28,6 @@ import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import java.nio.file.Path; import java.util.List; import java.util.Locale; -import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.platform.facet.FacetPointers; @@ -73,8 +72,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons } @Override - public void sendMessage(@NonNull Identity identity, @NonNull Component message, - @NonNull MessageType messageType) { + public void sendMessage(@NonNull Component message) { componentLogger.info(message); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 826df5b3..3cb64382 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -60,7 +60,6 @@ public enum ProtocolUtils { private static final GsonComponentSerializer PRE_1_16_SERIALIZER = GsonComponentSerializer.builder() - .downsampleColors() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() -- 2.39.5 From ffca982a9db4e0a6f542f8f608fd6286579c3538 Mon Sep 17 00:00:00 2001 From: Emil <12966472+Emilxyz@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:09:54 +0200 Subject: [PATCH 18/33] chore: drop adventure-platform-facet (#1843) --- gradle/libs.versions.toml | 1 - proxy/build.gradle.kts | 1 - .../proxy/connection/client/ConnectedPlayer.java | 3 --- .../com/velocitypowered/proxy/console/VelocityConsole.java | 3 --- 4 files changed, 8 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 479b30ce..1d7a88a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,6 @@ spotless = "com.diffplug.spotless:8.2.0" [libraries] adventure-bom = "net.kyori:adventure-bom:5.2.0" adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:5.2.0" -adventure-facet = "net.kyori:adventure-platform-facet:4.4.1" asm = "org.ow2.asm:asm:9.9.1" auto-service = "com.google.auto.service:auto-service:1.1.1" auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.1.1" diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index c7164c15..e6d48392 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -159,7 +159,6 @@ dependencies { implementation(libs.fastutil) implementation(platform(libs.adventure.bom)) implementation(libs.adventure.text.serializer.json.legacy.impl) - implementation(libs.adventure.facet) implementation(libs.completablefutures) implementation(libs.nightconfig) implementation(libs.bstats) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 3bf23d5d..e28b7c94 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -122,8 +122,6 @@ import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.key.Key; import net.kyori.adventure.permission.PermissionChecker; -import net.kyori.adventure.platform.facet.FacetPointers; -import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.resource.ResourcePackInfoLike; @@ -165,7 +163,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, .resolving(Identity.DISPLAY_NAME, player -> Component.text(player.getUsername())) .resolving(Identity.LOCALE, Player::getEffectiveLocale) .resolving(PermissionChecker.POINTER, Player::getPermissionChecker) - .resolving(FacetPointers.TYPE, player -> Type.PLAYER) .build(); /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index 9bb6635f..b2e20e5b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -30,8 +30,6 @@ import java.util.List; import java.util.Locale; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.permission.PermissionChecker; -import net.kyori.adventure.platform.facet.FacetPointers; -import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.text.Component; @@ -64,7 +62,6 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons .resolving(PermissionChecker.POINTER, VelocityConsole::getPermissionChecker) .resolving(Identity.LOCALE, (console) -> ClosestLocaleMatcher.INSTANCE .lookupClosest(Locale.getDefault())) - .resolving(FacetPointers.TYPE, (console) -> Type.CONSOLE) .build(); public VelocityConsole(VelocityServer server) { -- 2.39.5 From a5680fc223f3e99b7ac8ff1989bb4eda4ddb21a6 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 02:21:01 +0100 Subject: [PATCH 19/33] chore: Bump dependencies in alignment with paper --- gradle/libs.versions.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d7a88a1..b1f9943c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ configurate3 = "3.7.3" configurate4 = "4.2.0" flare = "2.0.1" -log4j = "2.25.3" +log4j = "2.26.0" netty = "4.2.15.Final" [plugins] @@ -29,16 +29,16 @@ configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.r configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" } configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" } disruptor = "com.lmax:disruptor:4.0.0" -fastutil = "it.unimi.dsi:fastutil:8.5.15" +fastutil = "it.unimi.dsi:fastutil:8.5.18" flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } jline = "org.jline:jline-terminal-jansi:3.30.6" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" -junit = "org.junit.jupiter:junit-jupiter:5.14.2" +junit = "org.junit.jupiter:junit-jupiter:6.0.3" jspecify = "org.jspecify:jspecify:1.0.0" kyori-ansi = "net.kyori:ansi:1.1.1" -guava = "com.google.guava:guava:33.5.0-jre" -gson = "com.google.code.gson:gson:2.13.2" +guava = "com.google.guava:guava:33.6.0-jre" +gson = "com.google.code.gson:gson:2.14.0" guice = "com.google.inject:guice:7.0.0" lmbda = "org.lanternpowered:lmbda:2.0.0" log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } @@ -46,7 +46,7 @@ log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "lo log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" } log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } -mockito = "org.mockito:mockito-core:5.21.0" +mockito = "org.mockito:mockito-core:5.22.0" netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" } netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "netty" } netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } -- 2.39.5 From 1abb3ba56a42b8266d0bd190a1790d2ce5fc9a35 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 11:55:53 +0100 Subject: [PATCH 20/33] Release 3.5.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d048feb7..06ea6143 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=3.5.0-SNAPSHOT +version=3.5.0 -- 2.39.5 From 4498f1e03d3ef8b2005bb19a430cc78665ea3923 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 16:56:22 +0100 Subject: [PATCH 21/33] Release 3.5.1 Bumps fill plugin to mitigate a deployment issue --- gradle.properties | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 06ea6143..5ee5ca17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=3.5.0 +version=3.5.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e00cea72..aa7e6304 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.25.3" netty = "4.2.15.Final" [plugins] -fill = "io.papermc.fill.gradle:1.0.10" +fill = "io.papermc.fill.gradle:1.0.12" shadow = "com.gradleup.shadow:9.3.1" spotless = "com.diffplug.spotless:8.2.0" -- 2.39.5 From c1cd71a4bc4bb011bd93e3570765a5870a29ebf6 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 19:05:05 +0100 Subject: [PATCH 22/33] Toolchain and gradle bump (Java 25+) --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e01f345a..e6582ce8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ subprojects { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(25)) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9faa9c83..962ed925 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ netty = "4.2.15.Final" [plugins] fill = "io.papermc.fill.gradle:1.0.12" -shadow = "com.gradleup.shadow:9.3.1" +shadow = "com.gradleup.shadow:9.5.1" spotless = "com.diffplug.spotless:8.2.0" [libraries] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19a6bdeb..a351597e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -- 2.39.5 From 0942e162c0b8c30ab47734bc3596de0f2ec9fe5c Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 19:06:13 +0100 Subject: [PATCH 23/33] bump version to 4.0.0-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a9fde939..1ccee529 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=3.6.0-SNAPSHOT +version=4.0.0-SNAPSHOT -- 2.39.5 From 7cb01b85f0bae4ddda0ede3408c323cbd83eabfe Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 20:54:54 +0200 Subject: [PATCH 24/33] [ci skip] Bump API source & docs link to java 25 --- api/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 0e3304f6..c8afb716 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -59,14 +59,14 @@ tasks { val o = options as StandardJavadocDocletOptions o.encoding = "UTF-8" - o.source = "21" + o.source = "25" o.use() o.links( "https://www.javadocs.dev/org.slf4j/slf4j-api/${libs.slf4j.get().version}/", "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/", - "https://docs.oracle.com/en/java/javase/17/docs/api/", + "https://docs.oracle.com/en/java/javase/25/docs/api/", "https://jd.papermc.io/adventure/${libs.adventure.bom.get().version}/", "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", ) -- 2.39.5 From 95694cba28f67c7c2a264daf699987027ceed0e5 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 20:17:21 +0100 Subject: [PATCH 25/33] Update jline to 4.3.1 --- build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts | 1 + gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts b/build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts index e38ef34d..2cf96517 100644 --- a/build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts +++ b/build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts @@ -32,5 +32,6 @@ tasks.withType { archiveVersion.get() } attributes["Implementation-Version"] = velocityHumanVersion + attributes["Enable-Native-Access"] = "ALL-UNNAMED" } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 962ed925..a93ee0c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ disruptor = "com.lmax:disruptor:4.0.0" fastutil = "it.unimi.dsi:fastutil:8.5.18" flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } -jline = "org.jline:jline-terminal-jansi:3.30.6" +jline = "org.jline:jline-terminal-ffm:4.3.1" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" junit = "org.junit.jupiter:junit-jupiter:6.0.3" jspecify = "org.jspecify:jspecify:1.0.0" -- 2.39.5 From 757711a5bc0e78a2cca8316cd2da4a95508c9529 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:08:30 +0200 Subject: [PATCH 26/33] Use correct max tab complete lengths for < 1.13, = 1.13 and > 1.13 (#1796) --- .../proxy/protocol/packet/TabCompleteRequestPacket.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java index dda4695b..df8aa056 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java @@ -31,8 +31,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TabCompleteRequestPacket implements MinecraftPacket { - private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048; - private @Nullable String command; private int transactionId; private boolean assumeCommand; @@ -97,9 +95,11 @@ public class TabCompleteRequestPacket implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.noLessThan(MINECRAFT_1_13)) { this.transactionId = ProtocolUtils.readVarInt(buf); - this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); + + // 1.13 only supports a max length of 256: https://bugs.mojang.com/browse/MC/issues/MC-132663 + this.command = ProtocolUtils.readString(buf, version.equals(MINECRAFT_1_13) ? 256 : 32500); } else { - this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); + this.command = ProtocolUtils.readString(buf, 32767); if (version.noLessThan(MINECRAFT_1_9)) { this.assumeCommand = buf.readBoolean(); } -- 2.39.5 From 5cc6b0b7e548d0fc50ce69e86c307774abc05b67 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:08:36 +0200 Subject: [PATCH 27/33] Use `SecureRandom` to generate verification/anti-MITM token (#1797) --- .../proxy/connection/client/InitialLoginSessionHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index f6e1cd6e..81116de9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -50,9 +50,9 @@ import java.net.http.HttpResponse; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.MessageDigest; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; @@ -65,6 +65,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class InitialLoginSessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(InitialLoginSessionHandler.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final String MOJANG_HASJOINED_URL = System.getProperty("mojang.sessionserver", "https://sessionserver.mojang.com/session/minecraft/hasJoined") @@ -280,7 +281,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { private EncryptionRequestPacket generateEncryptionRequest() { byte[] verify = new byte[4]; - ThreadLocalRandom.current().nextBytes(verify); + SECURE_RANDOM.nextBytes(verify); EncryptionRequestPacket request = new EncryptionRequestPacket(); request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded()); -- 2.39.5 From c690b4abfc46ea9d9313dddcce2b8c86df4cbad6 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:08:47 +0200 Subject: [PATCH 28/33] Strip pre-java-9 version check in `Metrics` as Velocity targets 21+ (#1836) * Strip pre-java-9 version check in `Metrics` as Velocity targets 21+ * Simplify Java version check by using `Runtime.version()` (entry still needs the system property to produce the same bStats metrics) * Reconstruct `java.version` system property through `Runtime.Version` * Update `javaVersion()` javadoc * Newline while we're here --- .../com/velocitypowered/proxy/Metrics.java | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java index 7feeb25f..39645c07 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java @@ -21,10 +21,8 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bstats.MetricsBase; @@ -120,38 +118,28 @@ public class Metrics { () -> server.getVersion().getVersion())); metrics.addCustomChart(new DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(); - String javaVersion = System.getProperty("java.version"); - Map entry = new HashMap<>(); - entry.put(javaVersion, 1); + Runtime.Version version = Runtime.version(); - // http://openjdk.java.net/jeps/223 - // Java decided to change their versioning scheme and in doing so modified the - // java.version system property to return $major[.$minor][.$security][-ea], as opposed to - // 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1", - // otherwise, 9+ - String majorVersion = javaVersion.split("\\.")[0]; - String release; - - int indexOf = javaVersion.lastIndexOf('.'); - - if (majorVersion.equals("1")) { - release = "Java " + javaVersion.substring(0, indexOf); - } else { - // of course, it really wouldn't be all that simple if they didn't add a quirk, now - // would it valid strings for the major may potentially include values such as -ea to - // denote a pre release - Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); - if (versionMatcher.find()) { - majorVersion = versionMatcher.group(0); - } - release = "Java " + majorVersion; - } - map.put(release, entry); - - return map; + return Map.of( + "Java " + version.feature(), + Map.of(javaVersion(version), 1)); })); } } -} \ No newline at end of file + /** + * Recreates the exact {@code java.version} system property value from a {@link Runtime.Version}. + * + *

Per JEP 223, {@code java.version} is + * {@code $VNUM(-$PRE)?}; the build and optional segments only appear in {@code java.runtime.version}. + * + * @param v the runtime version + * @return the value {@code java.version} would hold on this JVM + */ + private static String javaVersion(Runtime.Version v) { + return v.version().stream() + .map(Object::toString) + .collect(Collectors.joining(".")) + + v.pre().map(p -> "-" + p).orElse(""); + } +} -- 2.39.5 From 729a05066211fb2a3f203f06804514be39882eba Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:08:55 +0200 Subject: [PATCH 29/33] Fix build-time GraalVmProcessor warning (#1801) --- proxy/build.gradle.kts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index e6d48392..c1864e34 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -112,6 +112,15 @@ tasks { workingDir = file("run").also(File::mkdirs) standardInput = System.`in` // Doesn't work? } + + withType().configureEach { + options.compilerArgs.addAll( + listOf( + "-Alog4j.graalvm.groupId=${project.group}", + "-Alog4j.graalvm.artifactId=${project.name}" + ) + ) + } } val projectVersion = version as String -- 2.39.5 From b8d1f16b078dad392e3b2e76c742e21889c87767 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:09:03 +0200 Subject: [PATCH 30/33] Setup encryption before possible disconnect (#1754) --- .../client/InitialLoginSessionHandler.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 81116de9..6b63a6f8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -200,8 +200,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { } byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret()); - String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); + // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption + // is enabled. + mcConnection.enableEncryption(decryptedSharedSecret); + + String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString(); String url = String.format(MOJANG_HASJOINED_URL, urlFormParameterEscaper().escape(login.getUsername()), serverId); @@ -230,18 +234,6 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { return; } - // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption - // is enabled. - try { - mcConnection.enableEncryption(decryptedSharedSecret); - } catch (GeneralSecurityException e) { - logger.error("Unable to enable encryption for connection", e); - // At this point, the connection is encrypted, but something's wrong on our side and - // we can't do anything about it. - mcConnection.close(true); - return; - } - if (response.statusCode() == 200) { final GameProfile profile = GENERAL_GSON.fromJson(response.body(), GameProfile.class); -- 2.39.5 From da7427fb517b0c2dee9f2679a22ca61b3e0ecdf8 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 11 Jul 2026 22:09:11 +0200 Subject: [PATCH 31/33] Set TitleActionbarPacket's default action to SET_ACTION_BAR (#1802) --- .../proxy/protocol/packet/title/TitleActionbarPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java index f34983ea..6bab8dd6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java @@ -28,7 +28,7 @@ public class TitleActionbarPacket extends GenericTitlePacket { private ComponentHolder component; public TitleActionbarPacket() { - setAction(ActionType.SET_TITLE); + setAction(ActionType.SET_ACTION_BAR); } @Override -- 2.39.5 From 28c9f5a3564197989f1232c07147d09be7130472 Mon Sep 17 00:00:00 2001 From: Beanes <29002908+Beaness@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:09:17 +0200 Subject: [PATCH 32/33] Small optimization to prevent blocking netty threads on UUID.randomUUID() (#1781) * Small optimization to prevent blocking netty threads on UUID.randomUUID() * Change FastRandomUuid to be a valid uuid v4 * Update javadoc for spotless * Migrate method to VelocityTabListLegacy and add notice that it is insecure * Bring back deleted override --- .../proxy/tablist/VelocityTabListLegacy.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java index 037ab262..76cea841 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; @@ -95,7 +96,7 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList { entry.setLatencyInternal(item.getLatency()); } } else { - UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries + UUID uuid = generateInsecureRandomUuid(); // Use a fake uuid to preserve function of custom entries nameMapping.put(item.getName(), uuid); entries.put(uuid, (KeyedVelocityTabListEntry) TabListEntry.builder() .tabList(this) @@ -153,4 +154,17 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList { int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); } + + /** + * Generates a random UUID v4 using {@link ThreadLocalRandom}. The result is a structurally valid + * UUID v4 but is not cryptographically secure + * + * @return a new random {@link UUID} + */ + private static UUID generateInsecureRandomUuid() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + long msb = (random.nextLong() & 0xffffffffffff0fffL) | 0x0000000000004000L; // version 4 + long lsb = (random.nextLong() & 0x3fffffffffffffffL) | 0x8000000000000000L; // IETF variant + return new UUID(msb, lsb); + } } -- 2.39.5 From 4bd30bac3342e2a2ff4a53c7c2840c8d7fe87d18 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 11 Jul 2026 22:06:09 +0100 Subject: [PATCH 33/33] Bump netty to 4.2.16.Final --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a93ee0c6..f3e5a9ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ configurate3 = "3.7.3" configurate4 = "4.2.0" flare = "2.0.1" log4j = "2.26.0" -netty = "4.2.15.Final" +netty = "4.2.16.Final" [plugins] fill = "io.papermc.fill.gradle:1.0.12" -- 2.39.5