Skip to content

Access Control

Package: com.hypixel.hytale.server.core.modules.accesscontrol

Access control determines who can connect to the server. The built-in system provides bans and a whitelist, both UUID-based. Mods can register custom AccessProvider implementations for additional checks.

When a player connects, AccessControlModule handles PlayerSetupConnectEvent and queries all registered AccessProvider instances. Each provider returns a CompletableFuture<Optional<String>> — if any returns a reason, the event is cancelled and the player is disconnected with that message.

The built-in providers are checked in registration order (whitelist first, then bans), but all provider futures are combined with thenCombine — if providers return genuinely async futures, they may execute concurrently.

In singleplayer, a ClientDelegatingProvider is added that always allows access.

Register your own access control logic:

@Override
protected void setup() {
AccessControlModule.get().registerAccessProvider(uuid -> {
if (isMaintenanceMode() && !isAdmin(uuid)) {
return CompletableFuture.completedFuture(
Optional.of("Server is in maintenance mode.")
);
}
return CompletableFuture.completedFuture(Optional.empty());
});
}

The AccessProvider interface has a single method:

public interface AccessProvider {
CompletableFuture<Optional<String>> getDisconnectReason(UUID uuid);
}

Returning Optional.empty() means “allow” (no objection from this provider). Returning a reason string means “deny with this message”.

If you need external lookups (e.g. a ban database), cache locally and check the cache in your provider. Sync from the database in the background, so the provider always returns a completed future:

// keep an in-memory cache, synced from database in the background
private final Set<UUID> bannedUuids = ConcurrentHashMap.newKeySet();
@Override
protected void setup() {
// initial load + periodic refresh
getScheduler().scheduleRepeatingAsync(this::refreshFromDatabase, 0, 60, TimeUnit.SECONDS);
AccessControlModule.get().registerAccessProvider(uuid ->
CompletableFuture.completedFuture(
bannedUuids.contains(uuid)
? Optional.of("You are banned.")
: Optional.empty()
)
);
}

See Scheduling for the full scheduling API.

Kicking is a disconnect with a reason message:

playerRef.getPacketHandler().disconnect("You were kicked.");

The server sends a Disconnect packet with the reason string, then closes the connection. There is no dedicated “kick” method — disconnecting with a message is the kick.

Bans are UUID-based and stored in bans.json. Two ban types exist: permanent and timed.

PermanentInfiniteBan, never expires:

InfiniteBan ban = new InfiniteBan(targetUuid, bannerUuid, Instant.now(), "reason");

Disconnect message: "You are permanently banned! Reason: ..."

TimedTimedBan, expires after a duration:

TimedBan ban = new TimedBan(
targetUuid, bannerUuid, Instant.now(),
Instant.now().plus(Duration.ofHours(24)), // expires in 24h
"reason"
);

Disconnect message: "You are temporarily banned for <duration>! Reason: ..."

Expired timed bans are automatically pruned on load and on connection checks.

All ban types implement the Ban interface:

MethodReturn TypeDescription
getTarget()UUIDBanned player’s UUID
getBy()UUIDUUID of who created the ban
getTimestamp()InstantWhen the ban was created
getReason()Optional<String>Ban reason
isInEffect()booleanWhether the ban is currently active
getType()String"infinite" or "timed"

The built-in HytaleBanProvider has a public modify() method that allows adding, removing, and updating bans — and both InfiniteBan and TimedBan have public constructors. However, the banProvider field on AccessControlModule is private with no getter, so mods cannot obtain a reference to it through the public API.

The /ban command always creates permanent (InfiniteBan) bans — there is no built-in command for timed bans. TimedBan is registered as a ban parser (so it deserializes from bans.json), but no built-in code ever creates one.

For custom ban logic, register your own AccessProvider (see Custom Access Providers) or use PlayerSetupConnectEvent to block connections.

Mods can register custom ban types that are deserialized from bans.json:

AccessControlModule.get().registerBanParser("myCustomBan", MyCustomBan::fromJsonObject);

The whitelist is UUID-based and stored in whitelist.json. When enabled, only listed UUIDs can connect.

The HytaleWhitelistProvider is private on AccessControlModule — mods cannot directly modify it. Use the /whitelist commands or register a custom AccessProvider for your own allowlist logic.

The OP system is not a separate access control feature — it’s a permission group named "OP" with the "*" wildcard. Use PermissionsModule to manage it:

PermissionsModule perms = PermissionsModule.get();
// grant op
perms.addUserToGroup(playerUuid, "OP");
// revoke op
perms.removeUserFromGroup(playerUuid, "OP");
// check if op
Set<String> groups = perms.getGroupsForUser(playerUuid);
boolean isOp = groups.contains("OP");

See Permissions for the full permission system.

Connection events fire in a specific lifecycle order. Note that setup events and game events are mutually exclusive — if a player disconnects during setup (before fully joining), only the setup disconnect fires, not PlayerDisconnectEvent.

Fired when a player is connecting, before player data is loaded. Cancellable. No PlayerRef exists yet.

Runs on the Netty I/O thread — see threading.

MethodReturn TypeDescription
getUuid()UUIDPlayer’s UUID
getUsername()StringPlayer’s username
getAuth()PlayerAuthenticationAuthentication data
setCancelled(boolean)voidBlock the connection
setReason(String)voidDisconnect reason (shown to player)
referToServer(String host, int port)voidRedirect to another server
referToServer(String host, int port, byte[] data)voidRedirect with up to 4096 bytes of referral data
isReferralConnection()booleanWhether this player was referred from another server
getReferralData()byte[]Referral data from the sending server
getReferralSource()HostAddressAddress of the server that referred this player
eventRegistry.registerGlobal(PlayerSetupConnectEvent.class, event -> {
UUID uuid = event.getUuid();
// block connection
event.setReason("Not allowed.");
event.setCancelled(true);
// or redirect to another server instead of denying
event.referToServer("lobby.example.com", 21000);
});

This is where AccessControlModule checks bans and whitelist.

Fired after player data is loaded, before the player is added to a world. Has a PlayerRef and allows overriding which world the player joins.

Runs on an async thread (CompletableFuture pool) — see threading.

MethodReturn TypeDescription
getPlayerRef()PlayerRefThe connecting player
getWorld()WorldWorld the player will join
setWorld(World)voidOverride the target world
eventRegistry.registerGlobal(PlayerConnectEvent.class, event -> {
// redirect new players to a lobby world
if (isFirstJoin(event.getPlayerRef())) {
event.setWorld(lobbyWorld);
}
});

Fired when a fully-connected player leaves. Has PlayerRef, fires before the player is removed from their world.

Runs on the Netty I/O thread (or whichever thread called disconnect()) — see threading.

MethodReturn TypeDescription
getPlayerRef()PlayerRefThe disconnecting player
getDisconnectReason()PacketHandler.DisconnectReasonWhy the player disconnected
eventRegistry.registerGlobal(PlayerDisconnectEvent.class, event -> {
PacketHandler.DisconnectReason reason = event.getDisconnectReason();
// reason.getServerDisconnectReason() — if kicked/banned (server-initiated)
// reason.getClientDisconnectType() — if client left voluntarily
});

Fired when a player disconnects during setup — before they had a PlayerRef. Only has username/UUID/auth.

Runs on the Netty I/O thread — see threading.

MethodReturn TypeDescription
getUsername()StringPlayer’s username
getUuid()UUIDPlayer’s UUID
getAuth()PlayerAuthenticationAuthentication data
getDisconnectReason()PacketHandler.DisconnectReasonWhy the connection was dropped

All connection events are synchronous (IEvent, not IAsyncEvent). The event bus calls each listener inline on the calling thread — no thread hop occurs.

EventThread
PlayerSetupConnectEventNetty I/O thread
PlayerConnectEventAsync thread (CompletableFuture pool)
PlayerDisconnectEventNetty I/O thread (or caller of disconnect())
PlayerSetupDisconnectEventNetty I/O thread
CommandDescription
/ban <player> [reason]Permanently ban a player
/unban <player>Remove a ban
/whitelist enable/disableToggle whitelist
/whitelist add/remove <player>Manage whitelist entries
/whitelist list/status/clearView or clear whitelist
/op add/remove <player>Grant or revoke operator
/kick <player>Kick a player

See Access Control Commands for details.