How it works
A clear path from connection to decision, without hiding where an address goes.
The short version
A player connects and the plugin checks its local cache. A fresh result means no network request. On a miss, the configured lookup service finds the country and network, returns a normalized answer, and the plugin applies your country, ASN and VPN rules.
Detailed flow
1. Plugin receives a login
The plugin listens to the platform's pre-login event. On Bukkit that's AsyncPlayerPreLoginEvent. On BungeeCord it's LoginEvent. On Velocity it's LoginEvent with the async continuation API. The event gives us the connection IP, the player's name, and (sometimes) their UUID. The plugin then submits the IP to its internal GeoRestrictService.checkIp(...) on a background thread.
2. Cache check
The cache is a ConcurrentHashMap in memory, mirrored to geo_cache.json on disk via a debounced async save (5s window). Saves are atomic: write to .tmp, then Files.move(... ATOMIC_MOVE), with a non-atomic fallback for filesystems that don't support it.
A cache hit short-circuits the entire network path. The cached GeoResponse is fed straight into the rule evaluator. A cache miss goes to step 3.
3. Lookup request
The plugin sends the IP to the configured Worker over HTTPS. The plugin does not contain a provider URL or provider credential. Those choices remain behind the Worker so one plugin config does not have to know every lookup service.
4. Lookup service result
The public Worker keeps successful answers for up to one day. On a full miss it tries its configured data sources and uses an independent fallback server only if they are unavailable. If no route can answer, the plugin follows blockOnLookupFailure. Full deployment details belong in the GitHub README.
5. Rule evaluation
Three rules, in order. First match wins:
- Country — list match against
countryCodein the configured mode (ALLOWLIST / BLOCKLIST). - ASN — same logic for AS numbers.
- VPN — boolean flags (
isVpn,isHosting,isProxy) plus a case-insensitive keyword scan of the AS name.
6. Result
If a rule matches, the event is denied with the configured kick message. If nothing matches, the player proceeds normally. In both cases the Discord webhook fires if configured.
Privacy boundary
On a local cache miss, the player's IP leaves the Minecraft server for the configured Worker. A further miss can be sent to a geolocation provider or to the protected fallback server. Each external service has its own logging and retention rules. GeoRestrict does not run a player dashboard or analytics profile.
Read the Privacy Policy for the current services, retention periods and optional Discord data before installing the plugin for a public community.
Caching details
- Cache file:
plugins/GeoRestrict/geo_cache.jsonon the server where the plugin runs. - Loaded on startup, written on a 5s debounce after the last change, written synchronously on shutdown.
- Old entries are purged every 6h via a scheduled task (configurable TTL).
- Hard cap of
maxCacheEntries; oldest entries are evicted in order when exceeded. - On load, entries older than 365 days are dropped silently — that's your upper bound for "stale data after a long offline period".
- The public lookup service keeps successful answers for up to 24 hours.
Folia-specific note
The plugin detects Folia at startup and switches to its async scheduler for configuration checks, update checks and cache maintenance. GeoRestrict does not touch world state, so none of these tasks needs region ownership.