Welcome to the Hangar Open Beta. Please report any issue you encounter on GitHub!
Avatar for jamesmblakely

Preserves UUID-keyed player progress when a Paper server temporarily switches to offline mode during auth/WAN outages.

Report IdentityBridge?

IdentityBridge

Keep player progress connected when an online-mode Paper server must temporarily run in offline mode — because Mojang's auth services are down, or because your server can't reach them (for example a LAN server whose internet connection is out).

IdentityBridge is a small, admin-only utility plugin for Paper. Whenever an online-mode server can't reach Mojang/Microsoft login services — whether the services themselves are down or your connection to them is — it can stop accepting logins entirely. The usual emergency workaround is to switch the server to offline mode (online-mode=false) so people can keep playing — but offline mode hands every player a different, name-derived UUID, which disconnects them from their inventory, location, advancements, statistics, and every other piece of UUID-keyed progress.

IdentityBridge bridges that gap:

  • During normal online operation it remembers each player's authenticated identity (name → real online UUID).
  • While the server is temporarily in offline mode, it restores known returning players to their real online UUID at login, so they load their normal data.
  • For players it has never seen, it records a temporary offline identity so their data can be migrated back to the correct online UUID later — safely, with backups, lossless stats/advancement merging, and no silent data loss.
  • It monitors Mojang/auth availability and advises admins when a manual mode switch may be warranted. It never edits your config, switches modes, or restarts anything.

Important

Offline mode performs no authentication — anyone can connect using any username. Please read Security model and limitations before relying on this in production.


Table of contents


When would I use this?

Anything that temporarily cuts an online-mode server off from Mojang/Microsoft authentication is what IdentityBridge is for. The trigger differs; the failure mode — and the fix — is the same:

  • Mojang/Microsoft auth outages. The login/session services go down (it happens a few times a year) and nobody can join your server even though the server itself is fine.
  • Home/LAN hosting when the internet is out. The original motivation for this plugin: a server hosted on your local network. If your ISP has an outage, you still have LAN — everyone in the house can reach the server — but with no WAN the server can't reach Mojang to authenticate anyone. Switch to offline mode and keep playing on LAN; IdentityBridge keeps everyone on their real profiles and cleans up anything that needs it when the connection returns.
  • Planned internet downtime. ISP maintenance windows, moving your router/network around, or any scheduled period where the server host will be offline-but-reachable locally.
  • Unreliable connections. Rural, satellite, or mobile-hotspot connections where WAN access drops out often enough that occasional offline-mode stints are a fact of life.
  • Temporary off-grid setups. A LAN party, cabin trip, or classroom/venue network with no (or restricted) internet access, where you want people to play on their existing accounts and take their progress home intact.

In every case the workflow is the same: IdentityBridge learns identities during normal online play, keeps known players on their real UUIDs while you're offline, records anyone new, and migrates their data to the right account once authentication is available again.

Note

IdentityBridge is built for temporary offline operation with a return to online mode. It is not a tool for running a permanently offline (unauthenticated) server.


How it works

Minecraft assigns UUIDs differently depending on server mode:

Mode How the UUID is chosen Stable?
Online Random per-account UUID from Mojang (version 4) Yes, tied to the account
Offline nameUUIDFromBytes("OfflinePlayer:" + name) (version 3) Only as stable as the exact name
Bedrock (Floodgate) Derived from the XUID, new UUID(0, xuid) (version 0) Yes, tied to the XUID

Player data on disk (world/playerdata/<uuid>.dat, world/stats/<uuid>.json, world/advancements/<uuid>.json, …) is keyed by UUID. So when you flip from online to offline mode, returning players get a brand-new offline UUID and load an empty profile.

IdentityBridge handles this in two complementary ways:

  1. Login continuity (live). While the server is in offline mode, it intercepts each login and, for players it recognises from normal online operation, rewrites the connection's profile back to the player's real online UUID using Paper's AsyncPlayerPreLoginEvent profile API. The player loads their normal data immediately. Unknown players are let in under a temporary offline UUID (and recorded) or refused, per your config.

  2. Data migration (after the fact). For any data that was created under a temporary offline UUID — for example a brand-new player who joined during the outage — IdentityBridge can move that data to the correct online UUID once it is known, with backups and conflict protection. This also covers cases where login continuity was disabled or didn't apply.

These two mechanisms mean you never have to choose between "let people play" and "don't lose anyone's progress".


Requirements & compatibility

  • Server: Paper (or a Paper fork such as Purpur). The plugin relies on Paper-only profile APIs and will not work on stock Spigot/CraftBukkit. Folia is not supported: IdentityBridge uses the standard Bukkit scheduler, so Folia refuses to load it (clearly, at startup).
  • API line: built against the Paper 1.20.1 API with api-version: '1.20', using only long-stable APIs (AsyncPlayerPreLoginEvent profile rewriting, createProfileExact, Adventure components). Treat that as the compatibility floor, not a cap; smoke-test on your exact Paper build before relying on it in production.
  • Java: the jar bytecode targets Java 17, but you must run the server on the Java version required by your Paper build. Check Paper's requirements for the exact server version you deploy; newer Paper lines may require a newer Java runtime than the plugin's bytecode target.
  • Floodgate: optional. The plugin is fully functional on Java-only servers; Floodgate simply enables extra Bedrock-safe bookkeeping.

Installation

  1. Download IdentityBridge-1.0.0.jar (or build it — see Building from source).
  2. Drop it into your server's plugins/ folder.
  3. Start the server in normal online mode and let it run. IdentityBridge begins learning identities the moment players log in.
  4. (Optional) Open plugins/IdentityBridge/config.yml and adjust behaviour. Defaults are safe.

The plugin is most useful when it has been running in online mode before an outage, because that is when it learns who your players are. Installing it during an outage still works, but unknown players will need manual migration afterwards.


The outage workflow

A full, copy-pasteable runbook lives in docs/OUTAGE-PLAYBOOK.md. The short version:

When auth goes down (server still online):

  1. IdentityBridge logs and notifies admins: "auth looks unreachable while online." Confirm with /idb check.
  2. If players genuinely can't log in, stop the server, set online-mode=false in server.properties, and restart.
  3. Known players log straight back into their real data. New players are let in under temporary offline UUIDs and recorded.

When auth recovers (server still offline):

  1. IdentityBridge notifies admins: "auth looks healthy while offline." Confirm with /idb check.
  2. Stop the server, set online-mode=true, and restart.
  3. Returning players are auto-migrated on rejoin by default (migration.auto-migrate-on-rejoin): their temporary offline data folds into their real online UUID the moment they reconnect. If you turn on migration.auto-migrate-require-ip-match, auto-migration additionally requires the temporary outage record's IP to match the authenticated rejoin IP; anything that cannot be verified stays pending for manual review. Use /idb cache list offline to see who is still pending, and /idb migrate <name> (dry-run first, then --apply) for anyone who hasn't returned or whose auto-migration did not complete cleanly. See docs/MIGRATION.md.

IdentityBridge never performs steps 2 above for you — switching modes and restarting is always a deliberate admin action.


Commands

Base command: /identitybridge, aliases /idbridge and /idb. Everything requires identitybridge.admin.

Command What it does
/idb help List every subcommand with a one-line description, plus the migration flags.
/idb status Server mode, monitoring state, cache counts, last auth check, and any current advisory.
/idb check Probe the configured Mojang/auth endpoints and show per-endpoint status, HTTP code, and latency.
/idb lookup <name|uuid|xuid> Show everything known for a name, UUID, or Bedrock XUID: cached online UUID, the name-derived offline UUID, any temporary offline record, Bedrock info (with its linked Java account, resolved live for both global and local linking), and a live Mojang lookup.
/idb cache list [<java|offline|bedrock>] [page] List cached identities — all three caches (java, offline, bedrock) by default, or one type (also list all).
/idb cache add java <name> [onlineUuid] [--force] Manually remember a Java player. <name> must be a valid Java username (1-16 letters, numbers, or underscores — grandfathered legacy names shorter than Mojang's current 3-character creation minimum are accepted). With no UUID, it is resolved from Mojang; the name's capitalisation is corrected automatically. A non-v4 UUID, or an explicit UUID that Mojang says belongs to a different current name owner, is refused unless --force is supplied for deliberate testing/override use.
/idb cache remove <java|offline|bedrock> <id> Forget one cached identity. The id is a name/UUID for java & offline, or an XUID/name for bedrock.
/idb migrate <name> [flags] Migrate offline data for a Java username to the correct online UUID. Dry-run by default. Use migrate-uuid for unusual historical data that cannot be addressed by a valid Java username.
/idb migrate-uuid <src> <dst> [flags] Migrate data between two explicit UUIDs (works even if the offline player was never recorded).
/idb reload Flush in-memory identity data to disk, then reload config and identity data.

Migration flags: --apply (actually perform it — omit for a dry run), --overwrite (force the source file to win a .dat conflict instead of using play time, after backup), --remove-source (delete opaque source files after copying/cleanup; merged stats/advancements are always folded into the target and removed to prevent double-counting), --online-uuid <u> (must be an online-style Java UUID for /idb migrate; name-based migration rejects non-v4 targets even if they came from cached/captured metadata, so use migrate-uuid for an intentional arbitrary UUID target), --offline-uuid <u>, --offline-name <n> (must be a valid Java username; use --offline-uuid for an unusual source name).

When both UUIDs already have data, migration unifies them rather than skipping: stats are summed, advancements unioned, and an inventory (.dat) clash is resolved in favour of the file with more play time — an exact play-time tie is broken by the number of completed advancement criteria (a displaced file is always backed up before being overwritten — nothing is discarded without a backup). Empty/truncated source files are refused instead of copied or folded in as empty data. See docs/MIGRATION.md.

If /idb migrate <name> resolves the target UUID through Mojang and the applied migration completes cleanly, IdentityBridge also remembers that Java identity immediately for future offline-mode continuity. Dry-runs and explicit UUID-only migrations do not mutate the known-player cache.

If a temporary offline record's captured target UUID disagrees with the current known-player cache for the same name, /idb migrate <name> refuses to guess and asks you to supply --online-uuid <uuid> after confirming the correct account.

Example:

/idb migrate Steve                          # preview what would happen
/idb migrate Steve --apply                  # do it (keeps opaque source files)
/idb migrate Steve --apply --remove-source  # do it and delete redundant opaque source files
/idb migrate-uuid 0a1b... 069a79f4... --apply

Permissions

Deliberately minimal for a small admin-only utility:

Permission Default Grants
identitybridge.admin op All /idb subcommands.
identitybridge.notify op Receive in-game advisories about auth outages and mode mismatches.

Configuration

config.yml is fully commented; see docs/CONFIGURATION.md for a reference. Highlights:

  • login-continuity.enabled — restore known players' identities in offline mode (default true).
  • login-continuity.block-unknown-during-outage — refuse never-before-seen players while offline (default false — let them in and record them).
  • login-continuity.require-ip-match — best-effort hardening: only restore an identity from the player's last-known IP (default false). require-ip-match-strict decides what happens when the IP can't be verified (no IP on record): keep it false to admit them anyway, or true to refuse.
  • login-continuity.proxy-mode — set true behind BungeeCord/Velocity so IdentityBridge never rewrites proxy-forwarded UUIDs (default false).
  • monitoring.* — how often to probe auth, which endpoints, and how/when to advise admins.
  • migration.* — backups, source-removal default, auto-migrate-on-rejoin (default true), optional auto-migration IP check (default false), and the list of UUID-keyed relative file locations (extensible).
  • privacy.store-player-ip — whether to persist players' last-known IPs at all (default true). Turning it off leaves require-ip-match with nothing to compare against: lenient mode admits players unverified, while require-ip-match-strict: true refuses unverifiable known players. It also prevents migration.auto-migrate-require-ip-match from verifying the outage IP if you enable that option, so those records stay pending for manual /idb migrate.

The config upgrades itself across plugin versions: new keys are merged in with their defaults, your values are kept, and a timestamped backup of the old file is written first.


Bedrock / Floodgate support

IdentityBridge works on Java-only servers and has no hard dependency on Floodgate. If Floodgate is installed, it:

  • detects Bedrock players via the Floodgate API (a username-prefix match alone is never trusted for login decisions, since anyone can type a prefixed name in offline mode — only an API confirmation or a Floodgate-derived UUID counts),
  • records their identity keyed by XUID (the stable Bedrock identifier) — never by display name alone — along with their Floodgate UUID, linked Java UUID, and linked/unlinked state,
  • never rewrites Bedrock UUIDs.

This is intentional: Floodgate UUIDs are derived from the XUID and are already stable across an online/offline switch, so Bedrock players keep their data automatically. IdentityBridge simply records the identity safely for admin visibility and never makes Java handling worse. All Floodgate interaction is done reflectively and degrades gracefully if Floodgate is absent or its API changes.

Account linking (global and local)

Floodgate can link a Bedrock account to a Java account so the two share one set of player data. IdentityBridge supports both linking methods through Floodgate's own API, which exposes them identically, so the same code path handles each (best-effort, via reflection, with no hard dependency on Floodgate):

  • Global linking — GeyserMC's hosted linking service (link.geysermc.org).
  • Local linking — a database (SQLite/MySQL) the server admin runs.

How IdentityBridge behaves with a linked Bedrock player:

  • They join under their linked Java UUID, so their data is already keyed exactly like their Java account and is naturally stable across an online ⇄ offline switch. IdentityBridge never rewrites it.
  • The link is read live for online players and can be resolved for offline players through Floodgate's link manager (the same call for global and local links), so /idb lookup can show the linked Java account either way.
  • The linked Java account is also recorded as a known player (bedrock.record-linked-java-account, default on), so that same account gets Java-side login continuity too if it ever connects from a Java client during an outage.

/idb status shows whether linking is active and whether it is global or local; /idb lookup <name|uuid|xuid> cross-references a Bedrock record with its linked Java account (and flags when that Java account is already known). Unlinked Bedrock players keep their stable XUID-derived UUID and are recorded for visibility only.


Security model and limitations

IdentityBridge is honest about what is and isn't possible. Please read this section.

  • Offline mode is unauthenticated. While online-mode=false, anyone can connect using any username. With login continuity enabled, an impersonator connecting with a known player's name will be placed onto that player's real data — this is an inherent property of offline mode, not something any plugin can prevent. Mitigations: keep offline mode as brief as the outage requires; use a whitelist and/or firewall while exposed; consider require-ip-match: true; or set block-unknown-during-outage: true.
    • Note on require-ip-match: it can only check players for whom an IP was previously stored. A player learned by the startup backfill (or with privacy.store-player-ip: false) has no IP on record; by default such a player is still admitted (fail-open) so legitimate users aren't locked out. Set require-ip-match-strict: true to refuse unverifiable logins instead.
  • Automatic migration can be made stricter than manual migration. If migration.auto-migrate-require-ip-match is enabled, auto-migration on rejoin only runs when the temporary offline record's stored IP matches the authenticated rejoin IP. If the IP is missing or different, IdentityBridge captures the online UUID and leaves the record pending so an admin can run /idb migrate after review.
  • Auth monitoring is heuristic. Reaching an endpoint is a good signal but not a guarantee that logins will succeed, and a transient network blip can produce a false reading. That is why monitoring is advisory only and why mode switches are always left to you.
  • Mojang lookups can fail. The public name→UUID API can be rate-limited or unavailable — especially during the very outage you're responding to. When it can't resolve a name, provide the online UUID explicitly (--online-uuid, or /idb cache add java <name> <uuid>).
  • It can't invent history. If a player never joined while the server was online, there is no learned online UUID to map to until one is determined (by them rejoining once online, by a Mojang lookup, or by you supplying it).
  • Names matter. Offline UUIDs are derived from the exact name used at login. Capitalisation is handled carefully, and migration lets you specify the exact offline name/UUID when needed. If a Java player changes their username after their last authenticated join, IdentityBridge will learn the new name the next time they join online; before then, refresh the cache with /idb cache add java <newName> <onlineUuid> so offline-mode continuity can recognise the new spelling during an outage.
  • Proxies are different. Behind BungeeCord/Velocity with UUID forwarding, real UUIDs are already supplied; IdentityBridge refuses to rewrite non-offline-style UUIDs and you should also set proxy-mode: true. With proxy-mode on, the offline-mode login rules don't apply at all — nothing is rewritten, blocked, IP-checked, or recorded as a temporary offline identity, because the proxy owns authentication; IdentityBridge only learns forwarded online-style UUIDs as known players. If your proxy has to be switched to offline mode during an outage, that is outside IdentityBridge's scope — it runs on the backend and cannot see or manage the proxy's mode.

When something can't be done safely, IdentityBridge does the safe thing: it reports clearly and preserves your data rather than guessing.


Data & files

Everything lives under plugins/IdentityBridge/:

File / folder Purpose
config.yml Configuration (self-upgrading, backed up before upgrades).
identities.yml All cached identities in one file, under java:, offline:, and bedrock: sections (each keyed by UUID or XUID), plus an ignored: list of UUIDs the startup backfill found unresolvable and a schema-version marker. Written atomically (temp file + rename), so a crash mid-write can't corrupt it.
backups/ Timestamped backups taken before any migration that writes or replaces files.
config.yml.backup-* Backups of the previous config, written before an automatic upgrade.
config.yml.corrupt-* A preserved copy of an unreadable config.yml; the plugin refuses to continue with guessed/default settings until the config is fixed or replaced.
identities.yml.corrupt-* A preserved copy of an unreadable identities.yml (e.g. a hand-edit broke the YAML). The plugin refuses to continue with a guessed empty identity store until the file is fixed or replaced.

No silent data loss: risky operations are backed up and dry-run by default, conflicts are merged losslessly where possible, and an inventory clash is never resolved by discarding the fuller file without a backup.

Do not hand-edit identities.yml while the server is running. Runtime state is authoritative, and /idb reload saves that in-memory state before it reloads the file so live login data is not discarded. Stop the server first, or use the /idb cache commands.


Building from source

Requires a JDK (17 or newer) to launch Gradle; the Gradle wrapper is included, so you do not need Gradle installed. The build itself is pinned to a Java 21 toolchain (auto-downloaded by Gradle if you don't have one) for reproducible compilation and tests, while the shipped jar still targets Java 17 bytecode via --release, so it runs on any Paper build's required Java version from 17 up.

./gradlew build

The jar is written to build/libs/IdentityBridge-<version>.jar. Run the test suite on its own with ./gradlew test.

Which version am I building? main between releases carries the next version with a -SNAPSHOT suffix (e.g. IdentityBridge-1.0.1-SNAPSHOT.jar) — unreleased work in progress, honestly labelled as such. To build a published release exactly, check out its tag first:

git checkout v1.0.0
./gradlew build

Releases are built reproducibly, so a jar built from a release tag is byte-for-byte identical to the one published on the Releases page.


Release smoke test

The full release process (versioning, changelog, tagging, the automated draft release) is documented in docs/RELEASING.md. Before publishing a jar, run the manual Paper/Floodgate checklist in docs/RELEASE-SMOKE-TEST.md. It covers the real server event paths that unit tests cannot exercise, including offline profile rewriting, auto-migration on rejoin, case-only offline-name ambiguity, Floodgate linked/unlinked behavior, advisory reload state, and renamed-player cache freshness.


License

Released under the MIT License.

Information

CategoryAdmin Tools
Published onJuly 6, 2026
LicenseMIT
Downloads0
Stars1
Watchers1

Pinned Versions

Members