Data & Storage
Persist player and world data with a single annotation. Fields are auto-saved to NBT and automatically synchronized to the client — no boilerplate, no manual packet handling.
Introduction
The Data & Storage module lets you persist data attached to players, worlds,
or your entire server with almost zero configuration. You declare a plain Java class (a POJO),
annotate it with @EriData, and the framework handles everything:
- Automatic serialization — every field is read and written to NBT without any code on your part.
- Selective sync — mark only the fields the client needs with
@Sync; the rest stays server-side. - Death persistence — choose whether data survives player death via
persistOnDeath. - Multiple scopes — store data per-player, per-world, or globally across all worlds.
A Plain Old Java Object is just a regular Java class with fields. It has no special parent class and no interface to implement. EriAPI reads its fields through reflection, so your class stays clean and readable.
Setup
Initialize the module during preInit and register each data class you want the
framework to manage. Registration must happen before the first world load.
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
// 1. Initialize the Data module
DataManager.init();
// 2. Register every annotated class you want to persist
DataManager.register(PlayerStats.class);
DataManager.register(WorldQuests.class);
}
Always call DataManager.init() first, then register() for each
class. Registering before init() will throw an IllegalStateException.
@EriData — Class Annotation
Place @EriData on any POJO to make it managed by EriAPI. The annotation declares
where the data belongs and how it behaves.
import fr.eri.eriapi.data.EriData;
import fr.eri.eriapi.data.DataScope;
import fr.eri.eriapi.data.Sync;
import java.util.*;
@EriData(modId = "mymod", scope = DataScope.PLAYER, persistOnDeath = true)
public class PlayerStats {
// Synced to the client — the player can read these in their HUD
@Sync public int level = 1;
@Sync public float xp = 0f;
@Sync public int coins = 0;
// Server-only — never sent to the client
public String lastLogin = "";
public List<String> achievements = new ArrayList<>();
public Map<String, String> settings = new HashMap<>();
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
modId |
String | Yes | — | Your mod's ID. Used as the NBT namespace to avoid key collisions. |
scope |
DataScope | No | PLAYER |
Where the data is stored. See DataScope. |
persistOnDeath |
boolean | No | true |
When true, data is copied to the respawned player via PlayerEvent.Clone. Set to false to reset on death. |
Every field in your POJO must have a default value (e.g. = 0, = "",
= new ArrayList<>()). EriAPI uses these defaults when a player is seen for
the first time and has no stored data yet.
@Sync — Field Annotation
All fields in an @EriData class are automatically saved to NBT on the server.
However, only fields marked @Sync are transmitted to the client.
Use @Sync on fields the client actively needs — typically anything displayed in
a HUD, GUI, or scoreboard. Keep sensitive or heavy server-only data un-annotated to avoid
unnecessary network traffic.
@EriData(modId = "mymod", scope = DataScope.PLAYER)
public class PlayerData {
// Sent to the client — visible in the HUD
@Sync public int coins = 0;
@Sync public int level = 1;
// Stays on the server — not sent
public String lastIp = "";
public long lastLoginTimestamp = 0L;
public List<String> bannedWords = new ArrayList<>();
}
@Sync sends data server → client. The client never modifies
synced fields directly. To apply a change, modify the field on the server and call
DataManager.save() — the re-sync happens automatically.
DataScope — Enum
DataScope controls where the framework attaches the NBT data. Choose the scope
that matches the lifetime and ownership of your data.
PLAYERData is stored per-player. Each player has their own independent instance. Ideal for stats, inventory addons, currencies, or any player-specific state.
WORLDData is stored per-world (dimension). Different overworld / nether / end instances. Use this for world-specific quests, events, or map state.
GLOBALA single shared instance across all worlds and players on the server. Suitable for server-wide settings, global leaderboards, or shared events.
// Per-player data (default)
@EriData(modId = "mymod", scope = DataScope.PLAYER)
public class PlayerStats { ... }
// Per-world data
@EriData(modId = "mymod", scope = DataScope.WORLD)
public class WorldQuests { ... }
// Global data (shared across all worlds)
@EriData(modId = "mymod", scope = DataScope.GLOBAL)
public class ServerSettings { ... }
DataManager — Static API
DataManager is the single entry point for reading, writing, and persisting your
data objects. All methods are static — no instance is needed.
Player data
// Retrieve the data object for this player (creates a default instance on first call)
PlayerStats stats = DataManager.get(player, PlayerStats.class);
// Modify fields freely
stats.level++;
stats.coins += 100;
// Persist changes to NBT and re-sync @Sync fields to the client
DataManager.save(player, PlayerStats.class);
World data
// Retrieve the data object for the current world
WorldQuests quests = DataManager.getWorld(world, WorldQuests.class);
// Modify
quests.activeQuests.add("quest1");
// Persist
DataManager.saveWorld(world, WorldQuests.class);
Method reference
| Method | Description |
|---|---|
init() |
Initializes the module. Must be called once in preInit. |
register(Class) |
Registers an @EriData class so the framework knows how to serialize it. |
get(player, Class) |
Returns the live data object for the given player (PLAYER scope only). |
save(player, Class) |
Persists the player's data to NBT and triggers a sync packet for @Sync fields. |
getWorld(world, Class) |
Returns the live data object for the given world (WORLD scope only). |
saveWorld(world, Class) |
Persists world data to NBT and triggers any applicable sync. |
DataManager.get() returns a live reference. Changes you make are in memory only
until you call save(). Forgetting to save means data is lost on server restart
and the client never receives the updated values.
DataSerializer — Supported Types
EriAPI's serializer automatically handles the following Java types. Fields of any other type are silently skipped during serialization.
int, float, double, boolean, long, short, byteEnum.valueOf().If you declare a field of an unsupported type (e.g. ItemStack, a custom object,
or a nested list), the serializer will skip it without throwing an error. The field retains its
default value on the next load. Stick to the types listed above for reliable persistence.
Automatic Synchronization
EriAPI hooks into Forge's event system and triggers sync automatically. You never have to write a packet or an event handler yourself.
When a player joins the server, all their @Sync fields are sent to their client immediately.
On respawn, data is copied from the dead player entity to the new one (if persistOnDeath = true), then re-synced to the client.
When a player travels between dimensions (overworld ↔ nether ↔ end), their data is re-synced automatically after the teleport.
persistOnDeath in detail
EriAPI listens to PlayerEvent.Clone, which Forge fires whenever a new player entity
replaces an old one — both on death/respawn and on dimension travel. When persistOnDeath = true
(the default), the module copies all field values from the old entity's NBT to the new one before
the sync packet is sent.
// This class resets on player death (coins go back to 0)
@EriData(modId = "mymod", scope = DataScope.PLAYER, persistOnDeath = false)
public class DeathPenalty {
@Sync public int coins = 0;
@Sync public int streak = 0;
}
// This class survives death (level never lost)
@EriData(modId = "mymod", scope = DataScope.PLAYER, persistOnDeath = true)
public class Progression {
@Sync public int level = 1;
public List<String> unlockedSkills = new ArrayList<>();
}
Complete Example — RPG Stats
Here is a full working example of a typical RPG stats system using the Data & Storage module. It demonstrates class declaration, setup, reading, modifying, and saving data.
1. Declare the data class
package com.myrpg.data;
import fr.eri.eriapi.data.EriData;
import fr.eri.eriapi.data.DataScope;
import fr.eri.eriapi.data.Sync;
import java.util.*;
@EriData(modId = "myrpg", scope = DataScope.PLAYER)
public class RpgStats {
// Displayed in the player's HUD
@Sync public int level = 1;
@Sync public float xp = 0f;
@Sync public int health = 20;
@Sync public int mana = 100;
// Server-only history
public List<String> completedQuests = new ArrayList<>();
}
2. Register in preInit
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
DataManager.init();
DataManager.register(RpgStats.class);
}
3. Use in a game event
@SubscribeEvent
public void onKill(LivingDeathEvent event) {
if (!(event.getSource().getTrueSource() instanceof EntityPlayer)) return;
EntityPlayer player = (EntityPlayer) event.getSource().getTrueSource();
// Retrieve the live object
RpgStats stats = DataManager.get(player, RpgStats.class);
// Grant XP
stats.xp += 50;
// Level-up logic
if (stats.xp >= stats.level * 100) {
stats.level++;
stats.xp = 0;
player.sendMessage(new TextComponentString(
"Level up! You are now level " + stats.level
));
}
// Persist and sync to client
DataManager.save(player, RpgStats.class);
}
There is no NBT compound to build, no packet class to write, and no event handler to wire
for sync. EriAPI takes care of all of it the moment you call DataManager.save().