A multi-module Java 21 / Gradle project for extracting Minecraft Server Management Protocol (MSMP) OpenRPC schemas from server JARs and generating typed Java client bindings.
This README covers running the project locally. For integration guides - using the Gradle plugin
to generate your own client, or adding the Spring Boot starter to an application - see
docs/.
-
gradle-plugin/— Gradle plugin (com.github.yannicklamprecht.mc.management.schema-extractor) that:- Fetches Minecraft version manifests from Mojang
- Downloads server JARs for specified versions
- Executes data generation to extract protocol schemas
- Outputs OpenRPC schemas as JSON
-
client/— Java 21 client library providing:- WebSocket transport layer
- Automatic code generation from OpenRPC schemas
- Typed DTOs, records, enums, and API facades
-
spring-boot-starter/— Spring Boot autoconfiguration for the client:minecraft.management.servers.<id>.*configuration properties (url, secret, protocol version, auto-connect) for one or more named MSMP servers- Autoconfigures a
MinecraftManagementServerRegistryholding oneMinecraftManagementClient/MinecraftManagementSessionpair per configured server, and connects each on startup - Publishes a Spring
ApplicationEvent(seecom.github.yannicklamprecht.mc.management.spring.event) for every MSMP notification, stamped with the id of the server it came from
-
console/— Spring Boot web app built onspring-boot-starter:- Server-rendered dashboard using jte templates (
src/main/jte) - A JSON API under
/api/servers(list configured servers) and/api/servers/{serverId}/**(players, allowlist, operators, bans) for each one - A
/api/eventsServer-Sent-Events stream pushing every MSMP notification, from every configured server, live to the browser (each event carries itsserverId) - A plain-JS frontend (
src/main/resources/static/app.js, no build step) with a server selector driving the dashboard
- Server-rendered dashboard using jte templates (
The server/ module uses jpenilla's run-paper plugin
to download and run Paper without building the client or extracting schemas:
./gradlew :server:runServerThe task accepts the Minecraft EULA automatically. On first launch it copies
server/server.properties into the ignored server/run/ directory, enabling the
management server at ws://localhost:25585 with TLS disabled for local development.
The local-development authentication secret is LocalDevelopmentManagementSecret12345678.
Run the client in another terminal; Gradle supplies the secret automatically:
./gradlew :client:runSubsequent server runs preserve configuration changes and existing secrets, filling in
the default secret only when missing or blank. The client run task reads the secret
from server/run/server.properties, falling back to server/server.properties if
the local secret is missing or blank. An explicitly set MINECRAFT_MANAGEMENT_SECRET
environment variable takes precedence.
Worlds and other runtime files also
live in server/run/. Enter stop in the server console to shut it down.
server/build.gradle.kts registers one runServer* task per MSMP protocol/JSON-RPC version
generated under client/src/generated/java/.../v<protocol>/, each running a different Minecraft
version so every generated API version can be exercised against a real server:
| Task | Minecraft version | MSMP protocol | Generated package | Game port | Management port |
|---|---|---|---|---|---|
:server:runServerV1_0_0 |
1.21.10 | 1.0.0 | v1_0_0 |
25566 | 25586 |
:server:runServer |
1.21.11 | 2.0.0 | v2_0_0 |
25565 | 25585 |
:server:runServerV3_0_0 |
26.2 | 3.0.0 | v3_0_0 |
25567 | 25587 |
:server:runServerV3_1_0 |
26.3 | 3.1.0 | v3_1_0 |
25568 | 25588 |
Each task gets its own run directory (server/run/, server/run-v1_0_0/, server/run-v3_0_0/,
server/run-v3_1_0/) and its own seeded server.properties (server/server.properties,
server/server-v1_0_0.properties, ...), so any combination can run at the same time without port
or world conflicts - each blocks the console it's started in, so run each in its own terminal:
./gradlew :server:runServer # terminal 1 - Minecraft 1.21.11, protocol 2.0.0
./gradlew :server:runServerV1_0_0 # terminal 2 - Minecraft 1.21.10, protocol 1.0.0
./gradlew :server:runServerV3_0_0 # terminal 3 - Minecraft 26.2, protocol 3.0.0
./gradlew :server:runServerV3_1_0 # terminal 4 - Minecraft 26.3, protocol 3.1.0Point the console (or any client) at one of them per configured server id, matching each
server's management port from the table above - protocol-version can be left unset and is
auto-detected on connect (see below), so it's omitted here:
minecraft:
management:
servers:
v1_0_0:
url: ws://localhost:25586
secret: LocalDevelopmentManagementSecret12345678
v3_1_0:
url: ws://localhost:25588
secret: LocalDevelopmentManagementSecret12345678With the local Paper server running, start the web console in another terminal:
./gradlew :console:bootRunOpen http://localhost:8080 for the dashboard. The console connects to every configured MSMP server on startup and stays connected for as long as the app runs; every notification from each server (players joining/leaving, allowlist/operator/ban changes, saves, etc.) is pushed live to the page over Server-Sent Events.
Every server is configured by name under minecraft.management.servers.<id>.* (see
console/src/main/resources/application.yml) - there is no single-server shorthand, only the map:
| Property | Default | Description |
|---|---|---|
minecraft.management.servers.<id>.url |
ws://localhost:25585 |
MSMP WebSocket URL |
minecraft.management.servers.<id>.secret |
— | Bearer secret (required for this server to be configured) |
minecraft.management.servers.<id>.protocol-version |
auto-detected | MSMP protocol version to speak; leave unset to auto-detect (see below), or pin one explicitly |
minecraft.management.servers.<id>.auto-connect |
true |
Connect automatically on startup; set false for manual/test control |
minecraft:
management:
servers:
survival:
url: ws://localhost:25585
secret: ${SURVIVAL_SECRET}
creative:
url: ws://localhost:25586
secret: ${CREATIVE_SECRET}Every configured server gets its own websocket connection and shows up in the dashboard's server
selector, and in the API under GET /api/servers and /api/servers/{id}/**.
protocol-version is optional. Left unset (the default), each server's actual MSMP protocol
version is detected right after connecting instead of being assumed: there's no dedicated
"get MSMP version" RPC method, so this works by calling minecraft:server/status (supported by
every protocol version) and mapping its version.name (the Minecraft version, e.g. "1.21.11")
through a generated ProtocolVersions.MINECRAFT_VERSION_TO_PROTOCOL_VERSION table -
client/src/generated/java/.../ProtocolVersions.java, produced by the schema-extractor Gradle
plugin alongside the per-protocol-version DTOs/API classes from the same extracted schemas (see
Extract Protocol Schemas below). Rerunning
extractMinecraftManagementSchemas/generateMinecraftManagementSources regenerates this table, so
newer Minecraft/protocol versions are picked up without any hand-written mapping to maintain. A
Minecraft version the table doesn't recognize (older than minMinecraftVersion, or newer than the
last regeneration) falls back to the latest known protocol version, logged as a warning.
Set protocol-version explicitly on a server to skip this detection round-trip or to pin a
specific version regardless of what the server reports.
The checked-in console/src/main/resources/application.yml configures one server, id skyblock,
reading SKYBLOCK_MANAGEMENT_URL/SKYBLOCK_MANAGEMENT_SECRET (defaulting to
ws://localhost:25585, matching the local Paper server from :server:runServer above, when
unset). The :console:bootRun task auto-supplies SKYBLOCK_MANAGEMENT_SECRET the same way
:client:run does for the plain client, reading it from server/run/server.properties (falling
back to server/server.properties) - so :console:bootRun connects out of the box as long as the
local Paper server is already running.
A server that fails to connect (wrong port, offline, bad secret, ...) is logged as an error but
does not stop the console from starting or stop its other configured servers from connecting;
calls against it just fail with "Not connected" until it's fixed and the app is restarted. It
still shows up in the sidebar's server list, badged "offline" in red, unless console.ui.hide-offline-servers
is set to true (default false), in which case it's left out of the list entirely instead.
Each sidebar entry shows the server's Minecraft version (or "offline") rather than its MSMP protocol version - hover it to see the protocol version instead. The sidebar re-checks every configured server's connection state and Minecraft version every 10 seconds, independent of the currently selected server, since that's the only way a server going online/offline is ever reflected there.
Apply the plugin and configure package generation in your build.gradle.kts:
plugins {
id("com.github.yannicklamprecht.mc.management.schema-extractor")
}
minecraftManagement {
packageName.set("com.github.yannicklamprecht.mc.management") // Required: target package for generated code
onlyReleases.set(true) // default: true (only release versions, no snapshots)
minMinecraftVersion.set("1.21.9") // default: 1.21.9 (when MSMP was introduced)
// Optional: specify custom versions
// versions.set(listOf("1.21.9", "1.21.10"))
}Download Minecraft servers and extract OpenRPC schemas:
./gradlew extractMinecraftManagementSchemasAlias:
./gradlew extractProtocolSchemasSchemas are cached in .gradle/caches/minecraft-management-gradle-plugin/protocol-schemas/.
Building the project generates typed Java code from schemas automatically only on a first run,
i.e. when src/generated/java/ has no generated sources yet:
./gradlew buildOnce sources exist (including checked-in ones), later ./gradlew build runs compile them as-is
without re-downloading/re-extracting/re-generating. Run
./gradlew generateMinecraftManagementSources explicitly, or pass
-PminecraftManagement.autoGenerate=true, to force regeneration - see
docs/gradle-plugin/tasks.md for details.
Generated sources are placed in src/generated/java/ and automatically included in compilation.
Generated classes include:
- DTOs and Records — Strongly typed data structures for protocol messages
- Enums — Protocol constants and options
- API Facades — Version-specific API clients (e.g.,
com.github.yannicklamprecht.mc.management.generated.v1_0_0.MinecraftManagementApi)
| Property | Type | Default | Description |
|---|---|---|---|
packageName |
String | — | Required. Package name for generated sources (e.g., com.github.yannicklamprecht.mc.management.generated) |
onlyReleases |
Boolean | true |
Include only stable releases (exclude snapshots/RCs) |
minMinecraftVersion |
String | 1.21.9 |
Earliest version to extract schemas for |
versions |
List | empty | If specified, extract only these versions (overrides minMinecraftVersion) |
manifestUrl |
String | Mojang official | URL of Minecraft version manifest |
outputDir |
Directory | .gradle/caches/minecraft-management-gradle-plugin/protocol-schemas |
Schema output directory |
cacheDir |
Directory | .gradle/caches/minecraft-management-gradle-plugin/minecraft-servers |
Downloaded server JARs cache |
generatedSourcesDir |
Directory | src/generated/java |
Where generated Java code is written |
| Task | Aliases | Description |
|---|---|---|
extractMinecraftManagementSchemas |
extractProtocolSchemas |
Download servers and extract schemas |
generateMinecraftManagementSources |
generateMsmpSources, generateProtocolSources |
Generate typed Java from schemas (runs automatically during build only when no generated sources exist yet; see Auto-generation on build) |
cleanMinecraftManagementCache |
cleanCache |
Delete cached server JARs and extracted schemas |
cleanMinecraftManagementSources |
cleanGeneratedSources |
Delete generated Java sources |
Once sources are generated and the project builds, use the client:
// Import generated API (version-specific)
import com.github.yannicklamprecht.mc.management.generated.v1_0_0.MinecraftManagementApi;
import com.github.yannicklamprecht.mc.management.transport.MinecraftManagementClient;
// Create and connect client
var client = new MinecraftManagementClient(
new ObjectMapper(),
URI.create("ws://localhost:25585"),
"your-secret-key"
);
client.connect().join();
// Use version-specific API
var api = new MinecraftManagementApi(client);
// Now use api to call server endpoints...- Java 21+
- Gradle 7.0+
./gradlew buildpackageNameproperty is required: SetpackageName.set("...")in theminecraftManagementblock.- Schemas not extracted: Run
./gradlew cleanCache extractMinecraftManagementSchemasto re-download servers. - Generated sources not appearing: Run
./gradlew cleanGeneratedSources buildto regenerate.