Java 25 for Hytale
Modern Java features and patterns for Hytale plugin development.
Why Java 25?
Hytale server runs on Java 25 - you must use this version for plugin development.
Installation
Windows (Recommended)
Using winget (Windows Package Manager)
winget install EclipseAdoptium.Temurin.25.JDK
Verify installation
java --version
Manual Download
-
Go to Adoptium.net
-
Download Temurin 25 (LTS)
-
Run installer
-
Add to PATH if not automatic
macOS
brew install --cask temurin@25
Linux
Ubuntu/Debian
sudo apt install temurin-25-jdk
Fedora
sudo dnf install temurin-25-jdk
Key Java 25 Features for Hytale
Records (Data Classes)
Perfect for immutable data objects:
// Instead of verbose class with getters/equals/hashCode public record PlayerData(String name, int level, double health) {}
// Usage var data = new PlayerData("Steve", 10, 100.0); System.out.println(data.name()); // "Steve"
Pattern Matching
Cleaner type checks:
// Old way if (obj instanceof Player) { Player player = (Player) obj; player.sendMessage("Hello!"); }
// Java 25 way if (obj instanceof Player player) { player.sendMessage("Hello!"); }
Switch Expressions
String message = switch (gameMode) { case SURVIVAL -> "Good luck surviving!"; case CREATIVE -> "Build freely!"; case ADVENTURE -> "Explore the world!"; default -> "Welcome!"; };
Pattern Matching in Switch
Object entity = getEntity(); String type = switch (entity) { case Player p -> "Player: " + p.getName(); case NPC n -> "NPC: " + n.getType(); case Monster m -> "Monster: " + m.getName(); case null -> "No entity"; default -> "Unknown entity"; };
Sealed Classes
Restrict inheritance:
public sealed class GameEvent permits PlayerEvent, WorldEvent, BlockEvent { // Base event class }
public final class PlayerEvent extends GameEvent { // Cannot be extended further }
Virtual Threads (Project Loom)
Lightweight concurrency for async operations:
// Old way - platform threads are heavy new Thread(() -> loadPlayerData()).start();
// Java 25 way - virtual threads are lightweight Thread.startVirtualThread(() -> loadPlayerData());
// Or with executor try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> loadPlayerData()); executor.submit(() -> loadWorldData()); }
Text Blocks
Multi-line strings:
String json = """ { "name": "MyPlugin", "version": "1.0.0", "author": "YourName" } """;
Hytale-Specific Patterns
Null Safety
Always check for null components:
// Good var health = entity.getComponent(HealthComponent.class); if (health != null) { health.heal(10); }
// Better with Optional entity.getComponentOptional(HealthComponent.class) .ifPresent(h -> h.heal(10));
Functional Event Handling
// Lambda for simple handlers registerEventListener(PlayerJoinEvent.class, e -> e.getPlayer().sendMessage("Welcome!"));
// Method reference for reusable handlers registerEventListener(PlayerJoinEvent.class, this::onPlayerJoin);
private void onPlayerJoin(PlayerJoinEvent event) { // Complex logic here }
Stream API for Collections
// Filter and process players List<Player> onlinePlayers = getServer().getPlayers();
onlinePlayers.stream() .filter(p -> p.getLevel() > 10) .forEach(p -> p.giveReward("veteran_badge"));
// Count specific types long monsterCount = getWorld().getEntities().stream() .filter(e -> e instanceof Monster) .count();
Common Issues
Issue Solution
Wrong Java version Set JAVA_HOME to Java 25
Class not found Check Gradle compileOnly dependency
Unsupported class version Rebuild with Java 25 toolchain
IntelliJ uses wrong JDK Project Structure → SDK → Java 25
IDE Configuration
IntelliJ IDEA
-
File → Project Structure → Project
-
Set SDK to Java 25
-
Set Language Level to 25
Gradle (build.gradle.kts)
java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } }