bedrock-bench is a load benchmark for Minecraft Bedrock servers. It installs a server from a pinned, hash-checked download, connects headless bots spread out across a fresh world, and measures how the server copes: tick rate, tick-thread CPU per tick, process CPU and memory, how far TPS falls while the players' views load, and how long each player waits for its view. Every server gets the same workload, bots, phases and metric code. A small adapter per server starts it, reads its tick counter and stops it. There is no composite score.
Each server has one folder under servers/. Its server.toml pins the downloads with their sha256 and
names the version bbench run uses by default. Next to it are the config templates the harness renders, named like
the real files, any helper the server needs, and a README on how a run starts and what to watch for. Default versions
are in bold.
| Server | Language | Pinned versions | Folder | Notes |
|---|---|---|---|---|
| Bedrock Dedicated Server | C++ | bds@1.26.52.3 |
servers/bds/ |
The vanilla reference. The download needs the Minecraft EULA. |
| Endstone | C++ on BDS | endstone@0.11.14 |
servers/endstone/ |
Runs inside bds@1.26.52.3 on Python 3.13. Installed from PyPI with hash-locked dependencies. |
| LeviLamina | C++ on BDS | levilamina@26.51.6 |
servers/levilamina/ |
Windows only. Runs inside BDS 1.26.51.1, pinned with LeviLamina. Two of its packages need the LeviMC EULA. |
| PocketMine-MP | PHP | pocketmine@5.44.3 |
servers/pocketmine/ |
Speaks protocol 1001, so it needs the protocol 1001 bot. |
| Altay | PHP | altay@5.44.7 |
servers/altay/ |
Fork of PocketMine-MP 5 that speaks protocol 2193. |
| Nukkit | Java | nukkit@b3caad6 |
servers/nukkit/ |
Snapshot build, versioned by commit. Old snapshots get pruned, so keep a copy of the jar for --artifact-dir. |
| Nukkit-MOT | Java | nukkitmot@9cb0aba |
servers/nukkitmot/ |
Jenkins build 1592, versioned by commit. Jenkins keeps few builds, so keep a copy of the jar for --artifact-dir. |
| PowerNukkitX | Java | powernukkitx@3.0.5powernukkitx@nightly.86f70bc |
servers/powernukkitx/ |
The nightly runs on JDK 25. Upstream replaces the nightly jar with every build, so keep a copy for --artifact-dir. |
| Allay | Java | allay@0.14.0 |
servers/allay/ |
Speaks protocol 1001, so it needs the protocol 1001 bot. |
| Dragonfly | Go | dragonfly@1793369 |
servers/dragonfly/ |
Built from source, so it needs Go 1.26 or newer. |
| Chorus | Rust | chorus@5cffd3 |
servers/chorus/ |
Built from source with Rust 1.95.0 |
Evaluated, not supported: MiNET (needs a NetherNet bot), JSPrismarine (protocol 748, client-authoritative movement), SerenityJS (protocol 975, rejects the bots' login and stalls without console input) and Sculk (discontinued, a login and chat skeleton with no world).
PocketMine-MP, Altay, Nukkit, Nukkit-MOT, PowerNukkitX and Allay run on the PHP and JDK builds pinned in
servers/runtimes.toml. To add a server, see
.github/CONTRIBUTING.md.
bedrock-bench runs servers on Windows 10/11 x64 and Linux x86_64. Measure on bare metal: a run on a virtual machine
or WSL2 is recorded as vcpu and is not comparable with bare-metal runs.
- Python 3.11 or newer and uv.
- Go 1.25 or newer to build the load bot, and 1.26 or newer for Dragonfly.
- Rust 1.95.0 through rustup and a C compiler, only for Chorus.
- At least 4 logical CPUs for the bots on top of the CPUs the server gets.
git clone https://github.com/EndstoneMC/bedrock-bench.git
cd bedrock-bench
uv syncdocs/get_started.md covers preparing the machine and writing a machine profile.
A first run checks the machine, builds the bot, downloads one BDS build and runs a 4-bot smoke test. It takes about 15 minutes.
uv run bbench doctor # CPU layout, power plan, idle load, toolchains
uv run bbench bots build # the load bot, from source
uv run bbench fetch bds@1.26.52.3 --accept-eula # pinned download, checked against its sha256
uv run bbench smoke configs/smoke.toml bds@1.26.52.3 # 4 bots, short phases, strict checks--accept-eula accepts the Minecraft End User License Agreement for BDS, and the
LeviMC EULA when it fetches LeviLamina. Nothing from Mojang is stored in this repository. The smoke run is written to
runs/<session-id>/, and docs/get_started.md explains what is in it.
Every server runs the same workload, set by configs/spread-100.toml and the base it
inherits from, configs/spread.toml.
| Setting | Value |
|---|---|
| Players | 100 bots, starting to join 300 ms apart |
| Spread | One bot per cell of a grid 11 cells wide with 640 blocks between cells, so no two views overlap. Each bot walks a circle about 22 blocks in radius. |
| View distance | 10 chunks |
| Simulation distance | 4 chunks |
| Client-side generation | Off: the server generates and sends every chunk |
| World | New for every run, seed 20261010, survival, easy |
| Phases | Boot, 30 s idle without bots, join, settle while views load (5 to 15 min), 90 s steady window, stop |
| Repeats | 3 per server, in an order rotated each repeat, 60 s pause between runs, an invalid run rerun once |
| CPUs | The server gets one set of CPUs and the bots and harness another, split the same way for every server |
Each run records the machine, the OS and every version it used in its run.json. A report refuses to mix machines,
machine profiles or configs.
uv run bbench fetch --accept-eula # every server's default version
uv run bbench run configs/spread-100.toml # every default version, 3 rotated repeatsbbench run takes the config first. With no server tags it runs every server's default version as one session and
writes it to runs/<session-id>/: one directory per run, report.md, summary.json and the CSV files.
uv run bbench report runs/<session-id> renders the report again. A run takes 10 to 20 minutes, most of it waiting
for views to load, so a full session on Windows (8 servers, 24 runs) takes about 4 to 8 hours.
With no tags, bbench fetch and bbench run skip these servers and say why: Chorus until its player list decodes,
PocketMine-MP and Allay until the protocol 1001 bot can be built, and LeviLamina on Linux. The report lists them.
To run a subset, name the servers:
uv run bbench run configs/spread-100.toml bds@1.26.52.3 endstone@0.11.14LeviLamina runs inside BDS 1.26.51.1, an older build than bds@1.26.52.3, so its row notes that the session has no
vanilla run of that build.
On a machine that cannot drive 100 bots, use configs/spread-50.toml or
configs/spread-25.toml, the same setup with fewer bots. Results are comparable only within
one config, and the report names its config and bot count at the top.
Draw the figures from a session's CSV files into results/:
uv run scripts/plot_results.py runs/<session-id>Every server goes through the same phases with the same bots, samplers and metric code. TPS comes only from a counter that advances once per executed tick, read from the server console every 5 s, and crashes and failed runs are reported, never dropped. docs/methodology.md gives the rules and their limits, including a conflict-of-interest note: EndstoneMC maintains this benchmark and also develops Endstone. docs/metrics.md defines every metric.
The blog tells the story of the Endstone 0.11.14 performance work this benchmark grew out of.
@software{bedrock_bench,
author = {{EndstoneMC}},
title = {bedrock-bench: a load benchmark for Minecraft Bedrock servers},
year = {2026},
url = {https://github.com/EndstoneMC/bedrock-bench}
}Please also cite the harness version your runs record (harness.git in run.json).
Code and documentation are under Apache-2.0. Third-party notices are in NOTICE.
Not affiliated with Mojang Studios or Microsoft. "Minecraft" and "Bedrock" are trademarks of Mojang Studios and Microsoft.