Creating a Java Plugin
This guide walks you through creating a minimal Java plugin for Hytale using Gradle. By the end you will have a plugin that loads on the server and logs a message on startup.
Requirements
Section titled “Requirements”- Java 25 JDK installed. The game is built on Java 21 but runs on Java 25.
- A code editor of your choice.
- If your IDE doesnt include gradle project generation, install the Gradle CLI tool to create a gradle wrapper.
- Hytale installed via the official launcher (only needed to run a local dev server, not to build).
Project Structure
Section titled “Project Structure”A Hytale plugin follows standard Gradle/Java conventions. The only addition is a manifest.json that describes your plugin to the game.
- build.gradle.kts
- settings.gradle.kts
- gradle.properties
- gradlew / gradlew.bat
- .gitignore
Directorygradle/
Directorywrapper/
- gradle-wrapper.jar
- gradle-wrapper.properties
Directorysrc/
Directorymain/
Directoryjava/
Directorycom/example/myplugin/
- MyPlugin.java plugin entry point
Directoryresources/
- manifest.json plugin metadata
Directoryrun/ server working directory (gitignored)
- …
Setting Up the Project
Section titled “Setting Up the Project”-
Create a new Gradle project or clone an existing template. The Gradle wrapper is included so you don’t need Gradle installed globally — just run
./gradlewfrom the project root. -
Configure
settings.gradle.kts— set your project name. This becomes the base name for JAR files produced by Gradle.settings.gradle.kts rootProject.name = "MyPlugin" -
Configure
gradle.properties— define your project version and build settings. These properties are referenced bybuild.gradle.kts.gradle.properties # The current version of your project. Please use semantic versioning!version=0.0.1# The Hytale server version to build against. Available versions can be found# at https://maven.hytale.comserver_version=2026.01.24-6e2d4fc36# The release channel your plugin should be built and ran against. This is# usually release or pre-release. You can verify your settings in the# official launcher.patchline=release -
Set up
build.gradle.kts— this is the core of the build system. It adds the Hytale server JAR to your classpath, auto-updates your manifest, and provides tasks for development.build.gradle.kts plugins {java}java {toolchain {languageVersion = JavaLanguageVersion.of(25)}}// Configuration for the Vineflower decompiler dependency.val vineflower by configurations.creating {isCanBeResolved = trueisCanBeConsumed = false}repositories {mavenCentral()maven {url = uri("https://maven.hytale.com/release")}}dependencies {val server_version: String by projectimplementation("com.hypixel.hytale:Server:${server_version}")vineflower("org.vineflower:vineflower:1.10.1")}// Resolve the Hytale install directory. Set the HYTALE_HOME environment// variable to override the default path.val hytaleHome = System.getenv("HYTALE_HOME")?: "${System.getProperty("user.home")}/AppData/Roaming/Hytale"// Updates the manifest.json file with the version from gradle.properties.tasks.register("updatePluginManifest") {val manifestFile = file("src/main/resources/manifest.json")doLast {if (!manifestFile.exists()) {throw GradleException("Could not find manifest.json at ${manifestFile.path}!")}val slurper = groovy.json.JsonSlurper()@Suppress("UNCHECKED_CAST")val manifestJson = slurper.parse(manifestFile) as MutableMap<String, Any>manifestJson["Version"] = project.version.toString()val jsonOutput = groovy.json.JsonOutput.toJson(manifestJson)val prettyJson = groovy.json.JsonOutput.prettyPrint(jsonOutput)manifestFile.writeText(prettyJson)println("Updated manifest.json version to ${project.version}")}}tasks.named("processResources") {dependsOn("updatePluginManifest")}tasks.register<JavaExec>("runServer") {dependsOn("processResources", "classes")mainClass.set("com.hypixel.hytale.Main")classpath = sourceSets["main"].runtimeClasspathval patchline: String by projectval runDir = file("run")workingDir = runDirdoFirst {runDir.mkdirs()}args = listOf("--allow-op","--assets=$hytaleHome/install/$patchline/package/game/latest/Assets.zip")}// Vineflower decompiler tasks for generating readable source code.val genSourcesOutputDir = layout.buildDirectory.dir("generated/sources/vineflower")tasks.register<JavaExec>("genSources") {group = "decompilation"description = "Decompile the Hytale Server jar using Vineflower."mainClass.set("org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler")classpath = vineflowermaxHeapSize = "12g"val runtimeClasspathConfig = configurations.runtimeClasspathdependsOn(runtimeClasspathConfig)doFirst {val outputDir = genSourcesOutputDir.get().asFileval serverJar = runtimeClasspathConfig.get().resolve().firstOrNull {it.name.startsWith("Server-") && it.extension == "jar"} ?: throw GradleException("Could not find Hytale Server jar in runtimeClasspath.")outputDir.deleteRecursively()outputDir.mkdirs()args = listOf("-dgs=1", // Decompile generic signatures"-rsy=1", // Remove synthetic members"-ren=1", // Rename ambiguous membersserverJar.absolutePath,outputDir.absolutePath)}outputs.dir(genSourcesOutputDir)}tasks.register<Jar>("genSourcesJar") {group = "decompilation"description = "Package decompiled sources into a sources jar."dependsOn("genSources")from(genSourcesOutputDir)archiveClassifier.set("sources")}Key things this does:
- Pulls the Hytale server JAR from the official Maven repository as a compile dependency.
- Defines an
updatePluginManifesttask that syncsVersionfrom your Gradle properties intomanifest.jsonevery build. - Defines a
runServertask that launches a local Hytale server with your plugin loaded. - Defines
genSourcesandgenSourcesJartasks that use Vineflower to decompile the Hytale server for easier code exploration.
-
Create
manifest.json— this tells Hytale about your plugin. TheMainfield must point to your plugin’s entry class.src/main/resources/manifest.json {"Group": "MyGroup","Name": "MyPlugin","Description": "(The 'Group' field is your organization or team name, displayed to players. This is different from the 'maven_group' in gradle.properties, which is used for Maven publishing.)","Version": "0.0.1","Description": "My first Hytale plugin!","Authors": [{"Name": "YourName"}],"Website": "","ServerVersion": "*","Dependencies": {},"OptionalDependencies": {},"DisabledByDefault": false,"Main": "com.example.myplugin.MyPlugin","IncludesAssetPack": false}Field Description GroupYour organization or team name — displayed to players as the plugin’s author group NamePlugin display name VersionSemantic version (auto-updated by Gradle) MainFully qualified class name of your JavaPluginsubclassIncludesAssetPackSet trueif your plugin bundles game assetsServerVersionServer version constraint ( *for any) -
Add a
.gitignoreto exclude build artifacts and the server runtime directory..gitignore ### Gradle ###.gradlebuild/!gradle/wrapper/gradle-wrapper.jar!**/src/main/**/build/!**/src/test/**/build/### Hytale ###run/### IntelliJ IDEA ###.idea/*.iws*.iml*.iprout/!**/src/main/**/out/!**/src/test/**/out/### Eclipse ###.apt_generated.classpath.factorypath.project.settings.springBeans.sts4-cachebin/!**/src/main/**/bin/!**/src/test/**/bin/### NetBeans ###/nbproject/private//nbbuild//dist//nbdist//.nb-gradle/### VS Code ###.vscode/### Mac OS ###.DS_Store
Writing the Plugin
Section titled “Writing the Plugin”Every plugin needs a main class that extends JavaPlugin. This is the class referenced by Main in your manifest. The constructor runs when the plugin is loaded, and setup() is called when the server is ready for you to register functionality like commands and event listeners.
// Replace 'com.example' with your actual package namespace (e.g., com.yourcompany or io.github.yourusername)package com.example.myplugin;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;import com.hypixel.hytale.server.core.plugin.JavaPlugin;import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
public class MyPlugin extends JavaPlugin {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
public MyPlugin(@Nonnull JavaPluginInit init) { super(init); LOGGER.atInfo().log("Hello from " + this.getName() + " v" + this.getManifest().getVersion()); }
@Override protected void setup() { LOGGER.atInfo().log(this.getName() + " is ready!"); }}That’s it — this plugin will log two messages to the server console when it loads.
Running the Server
Section titled “Running the Server”-
Start the server using the
runServerGradle task:Terminal window ./gradlew runServer -
Authenticate the server. You must do this before any client can connect. In the server terminal, run:
auth login deviceThis prints a URL — open it in your browser and sign in with your Hytale account. Then persist the session so you don’t have to re-authenticate every time:
auth persistence EncryptedIf you cannot type into the server terminal, you can trigger authentication from code temporarily:
@Overrideprotected void start() {CommandManager.get().handleCommand(ConsoleSender.INSTANCE, "auth login device");}Remove this code after your server is authenticated.
-
Connect from the Hytale client. Open the game and connect to
Local Server. If it doesn’t appear automatically, add127.0.0.1manually. -
Verify the plugin. Check the server console for your plugin’s log messages.
Building for Distribution
Section titled “Building for Distribution”To build a shareable JAR file, run:
./gradlew buildThe output JAR is written to build/libs/. Players can install your plugin by placing the JAR in their %APPDATA%/Hytale/UserData/Mods folder.
Generating Decompiled Sources
Section titled “Generating Decompiled Sources”The build script includes tasks to decompile the Hytale server JAR using Vineflower, making it easier to explore the game’s code.
To generate decompiled sources:
./gradlew genSourcesThe decompiled Java files are written to build/generated/sources/vineflower/. You can also package them as a sources JAR for IDE integration:
./gradlew genSourcesJarThis creates a *-sources.jar in build/libs/ that your IDE can attach to the server dependency for inline source viewing.