A Minecraft Paper 1.21.1 plugin that federates multiple server instances together, enabling CDN-style distributed gameplay where all players across all instances can see and interact with each other as if they were on a single server.
- Low-Latency Sync: Uses Redis pub/sub and direct TCP connections for minimal latency
- Full Player Synchronization: Position, rotation, equipment, animations, health, effects
- Entity Synchronization: Sync mobs, items, projectiles across instances
- World State Sync: Block changes, explosions propagate to all instances
- Chat Federation: Cross-instance chat with full formatting support
- Combat Sync: Cross-instance PvP and PvE damage with knockback
- Virtual Players: Remote players appear as real entities with skins and animations
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Node US-East │ │ Node EU-West │ │ Node AP-South │
│ (Primary) │◄──►│ │◄──►│ │
│ Players: 50 │ │ Players: 30 │ │ Players: 20 │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
┌───────────┴───────────┐
│ Redis │
│ (Pub/Sub + State) │
└───────────────────────┘
- Paper 1.21.1+
- Java 21+
- Redis 7.0+ (for pub/sub messaging)
- Network connectivity between all nodes
-
Build the plugin:
./gradlew shadowJar
-
Copy
build/libs/Portal-1.0.0.jarto each server'splugins/folder -
Start each server once to generate the config
-
Configure each node (see Configuration section)
-
Restart all servers
Portal is highly configurable. All settings are in plugins/Portal/config.yml.
# Unique identifier for this node (auto-generated if empty)
node-id: ""
# Human-readable name for this node
node-name: "node-us-east-1"
# Region identifier for latency-based routing
region: "us-east"
# Is this the primary/host node?
is-primary: true
# Network Configuration
network:
redis:
enabled: true
host: "redis.example.com"
port: 6379
password: "your-redis-password"
direct:
enabled: true
bind-port: 25580
peers:
- host: "node2.example.com"
port: 25580
- host: "node3.example.com"
port: 25580See the generated config.yml for all available options with detailed comments.
Each node needs:
- The same world files (use a shared filesystem or sync mechanism)
- A unique
node-nameand optionallyregion - One node should be set as
is-primary: true - Redis connection details
- Direct peer connections for lowest latency
| Command | Description | Permission |
|---|---|---|
/portal status |
Show federation status | - |
/portal info |
Show node information | - |
/portal stats |
Show network statistics | - |
/portal debug <option> |
Toggle debug options | portal.admin.debug |
/pnode list |
List connected nodes | portal.admin.nodes |
/pnode info <id> |
Show node details | portal.admin.nodes |
/pnode ping [id] |
Ping nodes | portal.admin.nodes |
/psync <type> |
Force synchronization | portal.admin.sync |
/portalcan also be used as/por/ptl
| Permission | Description | Default |
|---|---|---|
portal.admin |
Full admin access | op |
portal.admin.nodes |
Manage federation nodes | op |
portal.admin.sync |
Force sync operations | op |
portal.admin.debug |
Debug commands | op |
- Player positions are broadcast every tick (50ms) using batched, compressed messages
- Each remote player is represented as a virtual entity on other nodes
- Skin data is synchronized for accurate player appearance
- Equipment, animations, and effects are synchronized in real-time
- Block changes are immediately broadcast to all nodes
- Changes are batched for efficiency when many blocks change at once
- Each node applies changes to maintain identical world state
- Damage events are broadcast with full knockback vectors
- Virtual players can be damaged and will relay damage to the source node
- The source node processes the damage and broadcasts health updates
- Direct TCP connections between nodes for lowest latency
- Binary protocol (Protobuf) for efficient serialization
- LZ4 compression for large payloads
- Message batching to reduce network overhead
- Interpolation on virtual player movement for smooth rendering
- Network: Each player generates ~1-2 KB/s of sync traffic
- CPU: Minimal overhead, async processing on dedicated threads
- Memory: ~1 KB per remote player for state tracking
- Recommended: Low-latency connections (<50ms) between nodes
- World generation should be synchronized (use same seed)
- Redstone timing may vary between nodes
- Complex mob AI may behave differently across nodes
- Container interactions need careful handling
- Check Redis connectivity:
/portal status - Verify nodes see each other:
/pnode list - Check latency:
/pnode ping - Enable debug logging:
/portal debug sync
- Use direct connections instead of Redis-only
- Check network path between nodes
- Consider regional node placement
- Force sync:
/psync all - Check block change batching settings
- Ensure all nodes have identical world files
./gradlew build./gradlew runServersrc/main/kotlin/org/mwynhad/portal/
├── Portal.kt # Main plugin class
├── config/ # Configuration handling
├── protocol/ # Message definitions & serialization
├── network/ # Network layer (Redis, Direct TCP)
├── node/ # Node management
├── sync/ # Sync managers (Player, Entity, World, Chat)
├── virtual/ # Virtual player entities
├── listener/ # Bukkit event listeners
├── command/ # Command handlers
└── metrics/ # Performance metrics
(c) Mwynhad Softworks LLC 2026 - present (under the MIT Licence)