Enterprise-Grade Ban Management & Stats Synchronization for Game Servers
BackendBridge is a high-performance Java backend solution for Minecraft-like game server ecosystems. The system provides:
✨ Centralized Ban Management - Synchronized ban/unban events across multiple servers
📊 Player Stats Aggregation - Store player statistics (playtime, kills, deaths)
🔐 Admin Web Interface - Intuitive dashboard for ban management
🔑 Token-Based Server Auth - Secure communication between game servers and backend
⚡ Real-Time Updates - Live notifications via Server-Sent Events
🟢 Presence Tracking - Online/offline player status synchronization
- Java 17+ (OpenJDK or Oracle JDK)
- Maven 3.8+
- MySQL 5.7+ or MariaDB 10.5+
cd /path/to/MakisImperium/BanBridgeProjekt/Backendmysql -u root -p < src/main/resources/schema.sqlOr with Docker:
docker run --name banbridge-db -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 mysql:8.0 mysql_native_passwordEdit src/main/resources/backend.yml:
web:
bind: "0.0.0.0"
port: 8080
db:
jdbcUrl: "jdbc:mysql://localhost:3306/banbridge?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true"
username: "banbridge"
password: "secure_password"
serverAuth:
enabled: true
token: "your-secret-token"
admin:
serverName: "MyServer"
rootPasswordHash: ""mvn exec:java -Dexec.mainClass="org.backendbridge.PrintPasswordHash" \
-Dexec.args="yourPassword"# Build
mvn clean package
# Start
java -jar target/BackendBridge-1.0-SNAPSHOT.jar
# Or with custom config
java -jar target/BackendBridge-1.0-SNAPSHOT.jar /path/to/backend.yml🎉 Backend running at http://localhost:8080
org.backendbridge
├── AppConfig ⚙️ YAML Configuration Management
├── BackendMain 🚀 Bootstrapping & Initialization
├── Db 🗄️ HikariCP Connection Pool
├── HttpApiServer 🌐 HTTP Router & Request Handler
├── AuthService 🔐 Server-Token Authentication
├── AdminAuth 👤 Admin Session Management
├── Json / JsonUtil 📦 Jackson JSON Utils
├── LiveBus 📡 Event Broadcasting (SSE)
├── PasswordUtil 🔑 PBKDF2 Password Hashing
└── repo/ 📊 Data Access Layer
├── AdminRepository 👨💼 Admin UI Rendering & Actions
├── BansRepository 🚫 Ban Change Sync
├── StatsRepository 📈 Player Stats Persistence
├── UsersRepository 👥 User Management
├── AuditRepository 📋 Audit Logs
├── PresenceRepository 🟢 Online/Offline Tracking
├── CommandsRepository 💬 Command History
└── MetricsRepository 📊 System Metrics
Loads YAML configuration (backend.yml), validates required fields, provides centralized access.
AppConfig cfg = AppConfig.load(Path.of("backend.yml"));
System.out.println(cfg.web().port()); // 8080HikariCP connection pooling with MySQL JDBC driver and UTF-8 support.
Db db = Db.start(config);
try (Connection c = db.getConnection()) {
// Use connection
}Server-to-Backend authentication via token headers (X-Server-Key, X-Server-Token).
AuthService auth = new AuthService(db, config.serverAuth().enabled());
if (auth.isAuthorized(httpExchange)) {
// Process request
}Cookie-based session management with 8-hour TTL and in-memory storage.
AdminAuth admin = new AdminAuth(dbUser, dbPassword);
if (admin.isLoggedIn(httpExchange)) {
// Show admin panel
}Embedded JDK HTTP Server with RESTful endpoints and HTML admin dashboard.
Tracks online/offline player status with two modes:
- Snapshot Mode: All listed players online, others offline
- Event Mode: Update only specified players
// Snapshot mode - mark all others offline
{
"snapshot": true,
"players": [{xuid, name, ip, hwid}, ...]
}
// Event mode - selective updates
{
"players": [{xuid, online: true/false}, ...]
}GET /api/server/healthResponse:
{
"status": "ok",
"serverTime": "2026-02-24T15:30:00.123Z",
"dbOk": true
}POST /api/server/stats/batch
Content-Type: application/json
X-Server-Key: server_1
X-Server-Token: secret_token_hereRequest:
{
"players": [
{
"xuid": "2533274790299905",
"name": "PlayerName",
"playtimeDeltaSeconds": 3600,
"killsDelta": 15,
"deathsDelta": 3
}
]
}GET /api/server/bans/changes?since=2026-02-24T12:00:00Z
X-Server-Key: server_1
X-Server-Token: secret_token_hereResponse:
{
"serverTime": "2026-02-24T15:30:00.123Z",
"changes": [
{
"type": "BAN_UPSERT",
"banId": 123,
"xuid": "2533274790299905",
"reason": "Hacking detected",
"createdAt": "2026-02-24T14:00:00Z",
"expiresAt": "2026-02-25T14:00:00Z",
"revokedAt": null,
"updatedAt": "2026-02-24T14:30:00Z"
}
]
}POST /api/server/presence/batch
Content-Type: application/json
X-Server-Key: server_1
X-Server-Token: secret_token_hereSnapshot Mode (Recommended):
{
"snapshot": true,
"players": [
{
"xuid": "2533274790299905",
"name": "PlayerName",
"ip": "192.168.1.100",
"hwid": "device_id"
}
]
}Response: Empty (200 OK)
GET /admin/login # Login page
GET /admin/players # All players
GET /admin/bans # Ban list
GET /admin/player?xuid=... # Player details
POST /admin/player/ban # Ban a player
POST /admin/player/unban # Revoke ban
GET /admin/logout # LogoutCREATE TABLE players (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
xuid VARCHAR(20) UNIQUE NOT NULL,
last_name VARCHAR(255),
last_seen_at TIMESTAMP(3),
online BOOLEAN DEFAULT FALSE,
online_updated_at TIMESTAMP(3),
last_ip VARCHAR(45),
last_hwid VARCHAR(255),
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3)
);CREATE TABLE player_stats (
player_id BIGINT PRIMARY KEY,
playtime_seconds BIGINT DEFAULT 0,
kills BIGINT DEFAULT 0,
deaths BIGINT DEFAULT 0,
kdr DECIMAL(5,2) GENERATED ALWAYS AS (
CASE WHEN deaths > 0 THEN kills / deaths ELSE kills END
) STORED,
last_update TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3),
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
);CREATE TABLE bans (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
player_id BIGINT NOT NULL,
reason VARCHAR(500),
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3),
created_by VARCHAR(255) NOT NULL,
expires_at TIMESTAMP(3),
revoked_at TIMESTAMP(3),
revoked_by VARCHAR(255),
updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE,
INDEX idx_updated_at (updated_at),
INDEX idx_player_id (player_id)
);class BanSyncClient {
private Instant lastSince = Instant.parse("1970-01-01T00:00:00Z");
public void syncBans() throws Exception {
String url = "http://backend:8080/api/server/bans/changes?since=" +
URLEncoder.encode(lastSince.toString(), "UTF-8");
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.header("X-Server-Key", "server_1")
.header("X-Server-Token", "secret_token")
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode root = new ObjectMapper().readTree(response.body());
for (JsonNode change : root.get("changes")) {
String xuid = change.get("xuid").asText();
String reason = change.get("reason").asText();
Instant expiresAt = parseTime(change.get("expiresAt"));
Instant revokedAt = parseTime(change.get("revokedAt"));
if (revokedAt != null) {
unbanPlayer(xuid);
} else if (expiresAt != null && expiresAt.isBefore(Instant.now())) {
unbanPlayer(xuid);
} else {
banPlayer(xuid, reason, expiresAt);
}
lastSince = change.get("updatedAt").asText();
}
saveLastSince(lastSince);
}
}Best Practices:
- ✅ Idempotent: Multiple ban/unban calls are safe
- ✅ Offline-Tolerant: Players can be offline
- ✅ Persistent: Store
lastSincebetween restarts - ✅ Polling-Loop: Handle paginated results
serverAuth:
enabled: true
token: "your-secure-token-min-32-chars"Required Headers:
X-Server-Key: server_1
X-Server-Token: <token_from_database>
- Cookie-based session:
BB_ADMIN_SESSION - TTL: 8 hours
- Credentials: DB username/password
- Hash: PBKDF2 (120,000 iterations)
Generate a hash:
mvn exec:java -Dexec.mainClass="org.backendbridge.PrintPasswordHash" \
-Dexec.args="mySecurePassword123"Output:
pbkdf2$120000$<salt>$<hash>
Configure in backend.yml:
admin:
rootPasswordHash: "pbkdf2$120000$<salt>$<hash>"web:
bind: "0.0.0.0" # Bind address
port: 8080 # HTTP port
db:
jdbcUrl: "jdbc:mysql://..."
username: "banbridge_user"
password: "strong_password"
# HikariCP Pool Config
# - Max: 10 connections
# - Connection Timeout: 30s
# - Idle Timeout: 10m
# - Max Lifetime: 30m
serverAuth:
enabled: true
token: "token_from_database"
admin:
serverName: "MyGameServer"
rootPasswordHash: ""
broadcastPrefix: "§8[§3◆§bBackendBridge§3◆§8] §r"
limits:
banChangesMaxRows: 1000- Max Connections: 10
- Connection Timeout: 30s
- Idle Timeout: 10 minutes
- Max Lifetime: 30 minutes
- Indexed:
bans.updated_at,bans.player_id - Bulk Operations:
INSERT ... ON DUPLICATE KEY UPDATE - Transaction Isolation: READ_COMMITTED
GET /api/server/bans/changes: ~50ms (1000 changes)POST /api/server/stats/batch: ~100ms (100 players)GET /admin/players: ~200ms (10,000 players)
// Publish event
LiveBus.publishInvalidate("players");
// Subscribe (SSE)
GET /events/stream?channel=playersChannels:
players- Player list updatedbans- Ban status changedstats- Player stats updatedpresence- Online/offline status
mvn testmvn exec:java -Dexec.mainClass="org.backendbridge.BackendMain"Health:
curl http://localhost:8080/api/server/healthStats:
curl -X POST http://localhost:8080/api/server/stats/batch \
-H "Content-Type: application/json" \
-H "X-Server-Key: server_1" \
-H "X-Server-Token: secret" \
-d '{
"players": [{
"xuid": "2533274790299905",
"name": "TestPlayer",
"playtimeDeltaSeconds": 3600,
"killsDelta": 10,
"deathsDelta": 5
}]
}'Ban Changes:
curl 'http://localhost:8080/api/server/bans/changes?since=2026-01-01T00:00:00Z' \
-H "X-Server-Key: server_1" \
-H "X-Server-Token: secret"Presence:
curl -X POST http://localhost:8080/api/server/presence/batch \
-H "Content-Type: application/json" \
-H "X-Server-Key: server_1" \
-H "X-Server-Token: secret" \
-d '{
"snapshot": true,
"players": [{
"xuid": "2533274790299905",
"name": "TestPlayer",
"ip": "192.168.1.100",
"hwid": "device_123"
}]
}'Error: Public Key Retrieval is not allowed
Solution: backend.yml includes allowPublicKeyRetrieval=true
Error: Bad credentials
Check: Username = DB user, Password = DB password
HTTP 413 Payload Too Large
Solution: Increase limits.banChangesMaxRows or implement pagination
MySQL Error 1205: Lock wait timeout exceeded
Fix:
- Increase connection pool size
- Prevent deadlocks with proper lock ordering
- Increase MySQL
max_connections
| Library | Version | Purpose |
|---|---|---|
| Jackson Databind | 2.17.2 | JSON processing |
| SnakeYAML | 2.2 | YAML configuration |
| MySQL Connector/J | 9.3.0 | Database driver |
| HikariCP | 5.1.0 | Connection pooling |
| SLF4J | 2.0.16 | Logging |
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/BackendBridge-1.0-SNAPSHOT.jar app.jar
COPY backend.yml backend.yml
EXPOSE 8080
CMD ["java", "-Xmx512m", "-jar", "app.jar", "backend.yml"]Build & Run:
docker build -t banbridge:latest .
docker run -p 8080:8080 -e DB_USER=... -e DB_PASSWORD=... banbridge:latestapiVersion: apps/v1
kind: Deployment
metadata:
name: banbridge
spec:
replicas: 3
selector:
matchLabels:
app: banbridge
template:
metadata:
labels:
app: banbridge
spec:
containers:
- name: banbridge
image: banbridge:latest
ports:
- containerPort: 8080
env:
- name: DB_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
livenessProbe:
httpGet:
path: /api/server/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10Health Endpoint:
curl http://backend:8080/api/server/healthLogs:
tail -f /var/log/banbridge/app.log- Fork the repository
- Create feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open pull request
- Java 17+ (records, text blocks)
- Jackson annotations for JSON
- Try-with-resources for resources
- SLF4J for logging
Proprietary - All rights reserved.
Issues: GitHub issues
Documentation: See /anleitung.txt
Team: BanBridge Development Team
Made with ❤️ by the BanBridge Team
Built for admins who are tired of “it says online, but he’s gone” — and want a system that stays correct under real-world conditions.