Using LeonsDB in your plugin
This guide shows how a Paper plugin stores its data in LeonsDB. A complete
working example is in connector/example in the repository.
Setup
-
Install the LeonsDB plugin on your Minecraft server and put a server token in
plugins/LeonsDB/config.yml(see Running the server). -
In your plugin, compile against the LeonsDB API only (the
leonsdb-apijar). Do not shade it: the LeonsDB plugin provides it at runtime. -
Add LeonsDB as a dependency in your
plugin.yml:depend: [LeonsDB]
Declare your data
@PlayerData(namespace = "practicepvp", collection = "stats")
public class Stats {
@Indexed public int kills;
public int deaths;
@Transactional public long coins = 100; // default for new players
public Location lastLocation;
public ItemStack[] kit;
public transient int streak; // transient: not stored
}
- Namespace: your plugin’s name. Collection: the kind of data. Both use lowercase letters, digits and underscores.
- Every field that is not
staticortransientis stored under its name. - The class needs a constructor without parameters. Field initializers are the defaults for new players.
Register your classes once, in onEnable:
@Override
public void onEnable() {
LeonsDB.register(Stats.class, Clan.class);
}
If LeonsDB is unreachable, or a field’s type conflicts with stored data,
register throws. Let it: running without your data would be worse.
Player data
A player’s data is loaded before they join, and kept in memory while they are online. Reading it never touches the network:
Stats stats = LeonsDB.player(player.getUniqueId()).get(Stats.class);
stats.kills++;
That is all. LeonsDB notices the change and saves it in the background, about once per second, when the player leaves and when the server stops. Forty kills between two saves become one “kills +40” write.
Use get and change fields on the main thread. Keep the reference only
while the player is online.
Money and other changes that must never be lost
Use update. The change is applied immediately and saved right away; the
future completes once LeonsDB has stored it durably:
LeonsDB.player(player.getUniqueId())
.update(Stats.class, s -> s.coins -= price)
.whenComplete((ok, err) -> {
// Back to the main thread before touching Bukkit.
Bukkit.getScheduler().runTask(plugin, () -> {
if (err == null) giveItem(player);
else player.sendMessage("Purchase not confirmed yet, try again.");
});
});
If the future fails, the change is not lost: it stays in memory and LeonsDB keeps retrying in the background. It only means it is not confirmed yet.
To save everything of a player immediately (for example before
transferring them to another server), call save(); it returns a future
too.
Global data
Data that does not belong to one player (clans, bans, settings) lives in global collections, keyed by any string:
@GlobalData(namespace = "practicepvp", collection = "clans")
public class Clan {
public UUID leader;
public List<UUID> members = new ArrayList<>();
}
LeonsDB.global(Clan.class).update("red", clan -> clan.members.add(uuid))
.thenAccept(clan -> ...);
Several servers may change the same record at the same time. update
re-reads the latest version and re-applies your change when that happens,
so no change is ever lost, but your change function may run more than
once: it must only modify the object it is given. Global operations are
asynchronous; never wait for them on the main thread.
Supported types
| Java type | Notes |
|---|---|
int, long, double, boolean and their boxed forms |
Whole numbers are saved as increments, so merged changes are exact. |
String, UUID, Instant, byte[] |
|
| Enums | Stored by name. |
List<T>, Map<String, T> |
Of any supported type, nested as you like. Elements must not be null. |
ItemStack, ItemStack[], Location |
Items use Paper’s own format and are upgraded automatically when Minecraft updates. |
float, short, byte and char are not supported; use double, int
or String. A null field is stored as “no value”.
Changing your data classes
- Add a field at any time. Existing players get the field’s default.
- Remove a field at any time. Its stored data is kept, so adding it back later restores it.
- Changing a field’s type is refused by the server (
SCHEMA_CONFLICT), because another version of your plugin could still be writing the old type. Use a new field name instead. - If stored data contains a value your plugin version cannot read (for example an enum constant that a newer version added), that field keeps its default in memory and is never written back, so the stored value stays intact.
What happens when things go wrong
| Situation | What LeonsDB does |
|---|---|
| LeonsDB is unreachable when a player joins | The player is asked to try again. They never play with empty data, which would later overwrite their real data. |
| LeonsDB goes down while players are online | Players keep playing. Changes stay in memory and are saved as soon as LeonsDB is back. |
| A player rejoins before their data finished saving | The join waits for the save, so it never loads outdated data. |
| The Minecraft server stops | Everything is saved first. If LeonsDB cannot be reached within 10 seconds, unsaved changes are written to plugins/LeonsDB/recovery/. |
| The Minecraft server crashes | Changes from the last moment (up to about one second) may be lost. Changes made with update were already confirmed. |
| A save request is answered but the answer is lost | LeonsDB detects this and never applies the change twice. |
Rules of thumb
- Call
get,updateandsaveon the main thread. - Never
join()orget()a LeonsDB future on the main thread. - Use
longfor money (in cents if you need fractions).doubleworks, but floating-point rounding is not what you want for currency.