A custom Minecraft server built on Minestom, featuring weapon systems, world loading, an API-backed persistence layer, and environment-driven configuration.
Abyss Network is a Minestom-based game server for Minecraft 1.21.11. It replaces the standard Mojang server stack with a lightweight, custom implementation, giving full control over gameplay, world loading, player data, and server lifecycle.
The server supports both a production mode (API-backed persistence via the Go backend and online-mode authentication) and a dev mode (no API calls, offline-friendly).
- Online-mode authentication via Minestom's
Auth.Online() - API-backed persistence — a Go backend service (
backend/) owns a MariaDB/MySQL database and exposes a small REST API; the Java server persists players and staff through it asynchronously - Kill/death tracking — weapon kills (via MineGun's custom health system) and vanilla deaths are tracked and synced to the database (
POST /players/{uuid}/stats) - Stats viewing —
/stats(own) and/stats <player>(anyone, online or offline) in-game; web dashboard athttps://stats.vardinsdev.org/with auto-refresh - Polar world format — worlds are loaded from
.polarfiles and saved on shutdown - Custom weapon system — Rifle and Rocket Launcher via the MineGun library
- Block placement rules — realistic block orientation and connection logic (stairs, slabs, fences, doors, signs, etc.)
- Gamemode switching — players with permission level ≥ 2 can change their own gamemode
- Graceful shutdown — world is saved to disk before the process exits
- Custom logger — colour-coded, timestamped console output with an ASCII banner on startup
- Environment-based config — all secrets and runtime flags live in a
.envfile, never in code
- Java 25 (required by Minestom 2026.x)
- Go 1.24+ (to build/run the backend)
- MariaDB or MySQL (production only — any recent version works;
backend/docker-compose.ymlspins one up) - A Polar-format world file at
worlds/world.polar - Gradle (the wrapper
./gradlewis included)
The server reads a .env file in the project root. The following keys are recognised:
| Key | Required | Description |
|---|---|---|
TYPE |
Yes | Set to dev to disable API persistence (no backend required). Any other value enables production mode. |
ABYSS_API_URL |
Production | Base URL of the Go backend (e.g. http://localhost:8080 or https://stats.vardinsdev.org/) |
ABYSS_API_TOKEN |
Production | Bearer token sent on all write requests. Must match the backend's ABYSS_API_TOKEN. |
The backend reads backend/.env (see backend/.env.example):
| Key | Required | Description |
|---|---|---|
ABYSS_HTTP_ADDR |
No | HTTP listen address (default :8080) |
ABYSS_API_TOKEN |
Yes | Bearer token required for write endpoints; the Java server sends the same value |
ABYSS_DB_HOST |
Yes | MariaDB/MySQL host (e.g. localhost or db) |
ABYSS_DB_PORT |
Yes | Database port (e.g. 3306) |
ABYSS_DB_NAME |
Yes | Database name |
ABYSS_DB_USER |
Yes | Database username |
ABYSS_DB_PASSWORD |
Yes | Database password |
Example .env for local development:
TYPE=devExample .env for production:
TYPE=prod
ABYSS_API_URL=http://localhost:8080
ABYSS_API_TOKEN=your-secret-token
⚠️ Never commit your.envfile. Add it to.gitignore.
Place your Polar-format world at:
worlds/world.polar
The server will log an error and continue without a world if the file is missing. You can export a world to Polar format using the Polar tooling.
./gradlew buildThe output JAR will be placed in build/libs/.
Start the backend (MariaDB + API) with Docker Compose:
cd backend
docker compose up -dThe Docker daemon is disabled at boot on the deployment host — re-enable it once with:
sudo systemctl enable --now dockerThe containers are owned by the abyss-backend systemd user service; the public
dashboard is served through the abyss-stats Cloudflare named tunnel
(cloudflared-stats service) at https://stats.vardinsdev.org/.
Then run the server with TYPE=prod and ABYSS_API_URL set in .env:
./gradlew run./gradlew runOr run the built JAR directly:
java \
--enable-native-access=ALL-UNNAMED \
--add-modules=jdk.unsupported \
-XX:+IgnoreUnrecognizedVMOptions \
-jar build/libs/abyssnetwork-1.0-SNAPSHOT.jarThe server binds on 0.0.0.0:25565 by default.
backend/ # Go persistence API
│ main.go # Config, DB connection, HTTP server, graceful shutdown
│ Dockerfile / docker-compose.yml # Containerised MariaDB + API
│ internal/store/ # database/sql access + embedded schema
│ internal/httpapi/ # REST handlers (players, staff, health)
│
src/main/java/org/vardinsdev/abyssnetwork/
│
├── Main.java # Server entry point, startup sequence
├── AbyssLogger.java # Colour-coded console logger
│
├── Database/
│ ├── Config.java # dotenv-driven config (TYPE, ABYSS_API_URL, ABYSS_API_TOKEN)
│ ├── ApiClient.java # Async HTTP client for the Go backend
│ ├── PlayerSync.java # Player row upsert on join
│ └── PlayerStats.java # Player row DTO for the API
│
├── staff/
│ ├── StaffManager.java # In-memory staff cache, write-through to API
│ ├── StaffMember.java / StaffRank.java
│ └── StaffSystemExtension.java # Staff join/spawn handling, vanish propagation
│
└── events/
├── PlayerConfiguration.java # Spawn point, staff permission levels
├── ChatHandler.java # Custom chat formatting
├── KillTracker.java # Kill/death tracking, syncs stats to API
└── GamemodeSwitcher.java # Permission-gated gamemode switching
Persistence is owned by the Go backend (backend/), not by the game server.
- The Go service connects to MariaDB/MySQL with a connection pool, creates the schema on startup, and exposes a small REST API (
POST/GET /players,GET /players/by-username/{username},POST /players/{uuid}/stats,GET/POST/DELETE /staff,GET /health). It also serves a stats dashboard atGET /stats. - The Java server never talks to the database directly.
ApiClient(Database/ApiClient.java) sends async HTTP requests viaHttpClient.sendAsync, so database I/O never blocks the tick thread. StaffManagerkeeps a fast in-memory cache for reads on join and write-throughs every mutation to the API (addStaff,updateStaff,removeStaff).PlayerSyncupserts the player's UUID/username on join; other stats are preserved by the backend on conflict.KillTracker(events/KillTracker.java) watches MineGun's custom health tag andPlayerDeathEventto credit kills and deaths, pushing deltas toPOST /players/{uuid}/statsasynchronously. It must be registered beforeHealthManagement.register()so it observes the health drop before MineGun resets it.
Write authentication: all mutating endpoints (POST /players, POST /players/{uuid}/stats, POST /staff, DELETE /staff) require Authorization: Bearer <token> where the token is ABYSS_API_TOKEN. The Java server reads the same variable from .env and sends it automatically. GET endpoints (stats dashboard, player lookups, staff listing) stay public, so the dashboard can be shared without exposing write access. If ABYSS_API_TOKEN is unset the API logs a warning and accepts writes without a token (local dev convenience).
Schema (created automatically by the backend on startup):
CREATE TABLE IF NOT EXISTS players (
uuid VARCHAR(36) PRIMARY KEY,
username VARCHAR(16) NOT NULL,
kills INT DEFAULT 0,
deaths INT DEFAULT 0,
team INT DEFAULT -1,
player_rank VARCHAR(32) DEFAULT 'default',
is_opped BOOLEAN DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS staff (
uuid VARCHAR(36) PRIMARY KEY,
last_known_name VARCHAR(16) NOT NULL,
rank VARCHAR(32) NOT NULL,
vanished BOOLEAN DEFAULT FALSE
);Player upserts use INSERT ... ON DUPLICATE KEY UPDATE so the username stays current while all other stats are preserved.
| Event class | Trigger | Behaviour |
|---|---|---|
PlayerConfiguration |
AsyncPlayerConfigurationEvent |
Sets spawn point; applies staff permission levels |
GamemodeSwitcher |
PlayerGameModeRequestEvent |
Allows gamemode change if permission level ≥ 2 |
PlayerSync |
AsyncPlayerConfigurationEvent |
Upserts the player row via the API (production only) |
When TYPE=dev is set in .env:
- No API calls are made —
ApiClientis disabled and player/staff data is not persisted - The server starts faster and works without any external infrastructure
Switch to any other value (e.g. prod) to enable online-mode auth and persistence via the Go backend.
| Library | Purpose |
|---|---|
Minestom 2026.07.12-26.2 |
Core server framework |
MineGun 1.0.3 |
Custom weapon system (Rifle, Rocket Launcher) |
Placement 0.1.0 |
Block placement rules |
Polar 1.15.1 |
Polar world format loader |
dotenv-java 3.2.0 |
.env file parsing |
fastutil 8.5.12 |
High-performance collections (transitive for Polar) |
SLF4J Simple 2.0.13 |
Logging backend |
- Fork the repository and create a feature branch
- Use
TYPE=devin your.envfor local work — no database needed - Follow the existing package structure (
events/,Database/, etc.) - Use
AbyssLoggerfor all console output rather thanSystem.out.printlndirectly - Open a pull request with a clear description of what changed and why