HomeJavaModsData synchronization
Data synchronization
ModsJava

Data synchronization

Cross-server player data sync for Paper & Folia 1.21.4+

⬇ Download on Modrinth

DataSynchronization

Cross-server player data sync for Paper, Purpur and Folia 1.21.4+

No dupes · No data loss · Folia-ready · Redis / RabbitMQ / SQL / MongoDB · History, rollback, audit and GUI tools


Overview

DataSynchronization keeps player data perfectly synchronised across an entire Minecraft network. It transfers inventories, ender chests, vitals, advancements, attributes, potion effects, locations, recipes, statistics, PersistentDataContainer (NBT) bytes and custom plugin data between servers.

Unlike legacy "last-write-wins" sync plugins, DataSynchronization is built to eliminate the dupe-and-data-loss class of bugs. Every save passes through a per-player FIFO queue and is fenced by a monotonic session-ownership token, so a write from a superseded peer is rejected before it can touch the database.


Requirements

Not supported: Bukkit and Spigot. The plugin relies on Paper-only APIs (native Adventure, PersistentDataContainer byte serialization, AsyncChatEvent, modern Paper metadata).


What gets synced

Every category can be enabled or disabled individually.


Core features

Storage and cluster

Platform and runtime

Reliability and safety

History and rollback

Live GUI tools

Update notifications


Admin tooling

Operations is a first-class feature surface, not an afterthought.


Modules and API

Third-party plugins can synchronise their own data through the SyncModule API without forking DataSynchronization.

Bundled modules: Vault economy and Advancements (with recipes and statistics toggles).


Translations

Six languages out of the box: English, Deutsch, Español, Português brasileiro, Français, Polski.


Comparison

Compared to HuskSync

Compared to legacy sync plugins


Quickstart

Single Paper server

storage-method: h2

cache:
  type: memory

messaging-service: none

Drop the jar into plugins/ and start the server.

Multi-server network

storage-method: mariadb
server-name: "survival-01"

cache:
  type: redis

messaging-service: redis

redis:
  address: redis.internal:6379
  password: "..."

data:
  address: maria.internal:3306
  database: datasync
  username: datasync
  password: "..."

All servers share Redis and MariaDB. Sessions, cache and messaging are Redis-backed. Each server gets its own server-name.


Recommendations

Storage backend

Redis

Not required for single-server setups. Strongly recommended for multi-server networks: Redis unlocks atomic session claims, a shared cache across servers and sub-millisecond pub/sub invalidation.

Sharding

Enable only when a single database is no longer sufficient. Most networks below 2,000 concurrent players run comfortably on a single MariaDB or PostgreSQL instance.

What not to sync


FAQ

Does it work on Folia? Yes. Capture runs on the player's entity-region thread via the platform scheduler; everything else runs off-thread.

Why not Spigot or Bukkit? The plugin uses Paper-only APIs (native Adventure, PersistentDataContainer byte serialization, AsyncChatEvent, modern Paper metadata) without Spigot equivalents.

What happens if the database goes down? The circuit breaker opens after a configurable number of consecutive failures, stops hammering the database, and resumes automatically when it recovers.

What happens if a server crashes mid-session? The session-ownership claim expires via TTL. Another server can then acquire the claim and load the player. Late writes from the crashed peer are rejected by the fencing token.

Can I sync my custom plugin's data? Yes. Implement SyncModule (or subclass AbstractSyncModule) and register it through the API or auto-discovery.

Can I restore only part of a snapshot? Yes. Use /ds restore <player> <#> --only inventory,enderchest or --except statistics.

How large are stored rows? With GZIP enabled (default), typical late-game players sit in the 10-40 KiB range.

Can I add my own language? Copy translations/en.yml to e.g. it.yml, translate, save. The file watcher reloads it live.

Will translations break when I update the plugin? No. Each bundled YAML carries a meta.version; outdated on-disk files are backed up to <name>.yml<random-suffix> and replaced with the new bundled version automatically.


Commands

Main command: /datasync (alias /ds).

Core
/ds status [--section <name>]
/ds reload
/ds version
/ds metrics [--reset]
/ds doctor [--quiet]
/ds debug <pipeline|session|sharding> [player] [--watch]
Player data
/ds saveall [--filter <regex>]
/ds saveandkick <player|*> [--reason ...]
/ds forcesync <player>
/ds lookup <player> [--section <name>] [--json]
/ds view [player]
/ds view-confirm
/ds edit <player> inventory|enderchest|stats
/ds compare <p1> <p2> [--section <a,b>] [page]
/ds pause <player|--list|--all> [--duration <Nm|Nh|Nd>] [reason]
/ds resume <player|--all> [confirm]
History and snapshots
/ds snapshot <player> [--note ...]
/ds history <player> [--contains ...] [--since 1h|7d] [page|pin <#>|unpin <#>]
/ds diff <player> <#1> <#2> [--section <a,b>] [page]
/ds restore <player> <#> [--only|--except types] [--preview] [--reason ...] [confirm]
Maintenance
/ds purge <player> [--reason ...] [--dry-run] [confirm]
/ds purge --inactive <days> [--reason ...] [--dry-run] [confirm]
/ds reset <player> --type <types> [--reason ...] [confirm]
/ds move <old-uuid> <new-uuid> [--dry-run] [confirm]
/ds export <file> [--inactive <days>] [--with-history] [--with-audits] [--with-errors] [--all]
/ds import <file> [--dry-run] [--skip-players] [--skip-history] [--skip-audits] [confirm]
/ds reshard <new-shard-count> [--export <dir>]
Cache
/ds cache info
/ds cache clear [player|confirm]
/ds cache refresh [player]
Audit and diagnostics
/ds audit [player] [--action a,b,c] [--by <name>] [--server <name>] [--contains <text>] [--since 1h|7d] [--until 1h|7d] [--export] [page]
/ds errors [--level WARNING|SEVERE] [--contains <text>] [--since 1h|7d] [page]
/ds watch <player|off>
/ds stats
/ds benchmark [N] [--concurrent <K>]
/ds test <player> [--count <N>]
/ds whoami
/ds whois <player>
/ds search <query> [--sources audit,errors] [--limit N] [page]
Modules
/ds module list [--enabled|--disabled] [page]
/ds module info <id>
/ds module enable <id>
/ds module disable <id>
/ds module reload [<id>] [--only <ids>]
/ds module export <id> <file>
/ds module import <id> <file>
Translations
/ds translations info
/ds translations reload
/ds translations set <lang>

Permissions

Parent grant (operators only):

datasync.admin

Self-service permission, default true:

datasync.command.whoami
Granular per-subcommand grants
datasync.command.status
datasync.command.reload
datasync.command.saveall
datasync.command.saveandkick
datasync.command.lookup
datasync.command.purge
datasync.command.forcesync
datasync.command.history
datasync.command.restore
datasync.command.export
datasync.command.import
datasync.command.cache
datasync.command.audit
datasync.command.diff
datasync.command.module
datasync.command.snapshot
datasync.command.doctor
datasync.command.metrics
datasync.command.version
datasync.command.compare
datasync.command.test
datasync.command.errors
datasync.command.watch
datasync.command.reset
datasync.command.benchmark
datasync.command.stats
datasync.command.move
datasync.command.view
datasync.command.view-confirm
datasync.command.edit
datasync.command.debug
datasync.command.reshard
datasync.command.pause
datasync.command.resume
datasync.command.translations
datasync.command.whois
datasync.command.search

Power-user permission, dupe-capable:

datasync.command.view.copy

Warning: grant datasync.command.view.copy deliberately. It enables shift-click item copying inside /ds view.


Developer API

The api module is published to https://repo.tmp0.cc/releases. Coordinates:

de.squarecodefx:datasynchronization-api:<version>

Capabilities:

Public packages
de.squarecodefx.datasynchronization.api
de.squarecodefx.datasynchronization.api.annotation
de.squarecodefx.datasynchronization.api.data
de.squarecodefx.datasynchronization.api.event
de.squarecodefx.datasynchronization.api.exception
de.squarecodefx.datasynchronization.api.module
Entry point
DataSynchronizationAPI api = DataSynchronizationProvider.get();
Example operations
api.getPlayerData(uuid);
api.getPlayerSnapshots(uuid, 10);
api.forceSyncPlayer(uuid);
api.patchPlayerField(uuid, "health", 20.0);
api.setSyncPaused(uuid, true);
api.publishCustomMessage("channel", "payload");
Verified by MCModsHub

These come from our own check of the pack file, not from the source page.

Explore more