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.

fr.eri.eriapi.data NBT backed Auto sync 3 scopes

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.
What is a POJO?

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.

Java — Main mod class (preInit)
@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);
}
Order matters

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.

Java — PlayerStats.java
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.
Always initialize fields

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.

Java — Selective sync example
@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 is one-directional

@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.

P
PLAYER

Data is stored per-player. Each player has their own independent instance. Ideal for stats, inventory addons, currencies, or any player-specific state.

W
WORLD

Data is stored per-world (dimension). Different overworld / nether / end instances. Use this for world-specific quests, events, or map state.

G
GLOBAL

A single shared instance across all worlds and players on the server. Suitable for server-wide settings, global leaderboards, or shared events.

Java — DataScope usage
// 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

Java — Player data (PLAYER scope)
// 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

Java — World data (WORLD scope)
// 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.
Always call save() after modifying

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.

Primitivesint, float, double, boolean, long, short, byte
String — stored as an NBT string tag.
Enum — stored as the constant name string, restored via Enum.valueOf().
List<String> — stored as an NBT list of strings.
Map<String, String> — stored as a flat NBT compound tag.
Unsupported types are silently skipped

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.

Player Login

When a player joins the server, all their @Sync fields are sent to their client immediately.

Death & Respawn

On respawn, data is copied from the dead player entity to the new one (if persistOnDeath = true), then re-synced to the client.

Dimension Change

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.

Java — Resetting data on death
// 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

Java — RpgStats.java
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

Java — Main mod class
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
    DataManager.init();
    DataManager.register(RpgStats.class);
}

3. Use in a game event

Java — XP gain & level-up
@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);
}
That's all you need

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().