public final class IP2LocationManager extends Object
This class provides a global access point to IP geolocation services while supporting live reconfiguration without service interruption. It ensures there is always an active instance available to serve requests, even during configuration changes.
IServiceStatusListenergetStatusDescription() for UI display┌─────────────────────────────────────────────────────────────────────────┐ │ 1. User calls reconfigure() or reconfigureAsync() with new config │ │ 2. Manager creates new IP2LocationService instance │ │ 3. New instance initializes (loads/downloads databases) │ │ 4. Manager waits for new instance to become ready │ │ 5. Manager atomically swaps the active reference │ │ 6. Old instance enters "draining" state: │ │ └─ Completes in-flight requests │ │ └─ Rejects new requests (routed to new instance) │ │ 7. After grace period (30s default), old instance is disposed │ │ 8. Optional: cleanupOldConfiguration() removes orphaned files │ └─────────────────────────────────────────────────────────────────────────┘
// Get singleton instance
IP2LocationManager manager = IP2LocationManager.getInstance();
// Initial configuration (blocking)
manager.configure(new IP2LocationService.Builder()
.downloadToken("your-token")
.database(Database.DB11)
.directory(Paths.get("/var/lib/ip2location"))
);
// Perform lookups (thread-safe, from any thread)
GeoLocation loc = manager.lookup("8.8.8.8");
if (loc.isOk()) {
System.out.println("Country: " + loc.getCountryCode());
System.out.println("Location: " + loc.getLocationDescription());
}
// Display status in UI
statusLabel.setText(manager.getStatusDescription());
// "IPv4+IPv6, IPv4 ready, IPv6 ready"
// On application shutdown
manager.shutdown();
IP2LocationManager manager = IP2LocationManager.getInstance();
// Configure with virtual threads (Java 21+)
manager.configureAsync(
new IP2LocationService.Builder()
.downloadToken("your-token")
.threadFactory((task, name, daemon) ->
Thread.ofVirtual().name(name).unstarted(task))
.database(Database.DB11),
(service, success, error) -> {
if (success) {
System.out.println("Service ready!");
}
}
);
// Wait for service to be ready before accepting traffic
manager.awaitReady(60, TimeUnit.SECONDS);
// Later: reconfigure without service interruption
manager.reconfigure(new IP2LocationService.Builder()
.downloadToken("new-token")
.database(Database.DB5) // Different DB type
.includeIPv6(true) // Enable IPv6
.directory(Paths.get("/var/lib/ip2location-v2")) // New directory
);
// Clean up files from old configuration
manager.cleanupOldConfiguration();
// Listen for status changes
manager.addStatusListener(new IServiceStatusListener() {
@Override
public void onStatusChanged(String newStatus) {
SwingUtilities.invokeLater(() -> statusLabel.setText(newStatus));
}
@Override
public void onDatabaseUpdated(boolean ipv4, boolean ipv6) {
System.out.println("Databases updated: IPv4=" + ipv4 + ", IPv6=" + ipv6);
}
});
// Check if update can be performed
if (manager.canUpdateDatabasesNow()) {
manager.updateDatabasesNow();
}
In a NetPhantom clustered server environment, multiple JVM instances of the
server run concurrently, each managed by a Cluster Controller. Each JVM has its
own IP2LocationManager singleton. The bootstrap layer
(IP2LocationBootstrap) sets the cluster index via setClusterIndex(int)
and gives each instance its own database directory by appending "#" + index
to the configured path (e.g. IP2Location#1/, IP2Location#2/).
Key cluster behaviors:
[IP2Location] configuration in
server.ini. Only the database path differs per instance.getClusterIndex() and isClusterInstance()
to determine the appropriate UI behavior.All methods are thread-safe. Lookups use read locks that allow high concurrency, while reconfiguration uses write locks to ensure atomic handoff. In-flight requests during reconfiguration will complete successfully using whichever instance was active when the request started.
When reconfiguring:
cleanupOldConfiguration()The manager tracks in-flight requests to each service instance. During reconfiguration, the old instance is kept alive until all its in-flight requests complete (or timeout).
IP2LocationService,
IP2LocationService.Builder,
IInitializationCallback,
IThreadFactory,
IServiceStatusListener,
IP2LocationBootstrap| Modifier and Type | Method and Description |
|---|---|
void |
addStatusListener(IServiceStatusListener listener)
Adds a status listener to receive service events.
|
boolean |
awaitReady(long timeout,
TimeUnit unit)
Waits for the manager to have a ready service.
|
boolean |
canTriggerRecovery()
Checks whether manual recovery can be triggered now.
|
boolean |
canUpdateDatabasesNow()
Checks whether databases can be updated now.
|
boolean |
cleanupOldConfiguration()
Cleans up files from the old configuration directory.
|
void |
clearRecoveryRecommendation()
Clears the recovery recommendation.
|
void |
configure(IP2LocationService.Builder builder)
Configures the manager with an initial service instance (blocking).
|
void |
configureAsync(IP2LocationService.Builder builder,
IInitializationCallback callback)
Configures the manager with an initial service instance (non-blocking).
|
int |
getActiveRequestCount()
Returns the current active request count.
|
int |
getAutoRecoveryAttempts()
Returns the number of automatic recovery attempts made.
|
int |
getClusterIndex()
Returns the cluster index of this server instance.
|
int |
getConsecutiveInitFailures()
Returns the count of consecutive initialization failures.
|
int |
getConsecutiveLookupErrors()
Returns the count of consecutive lookup errors.
|
static IP2LocationManager |
getInstance()
Returns the singleton manager instance.
|
LocalDate |
getIPv4DatabaseDate()
Returns the date of the currently loaded IPv4 database.
|
LocalDate |
getIPv6DatabaseDate()
Returns the date of the currently loaded IPv6 database.
|
YearMonth |
getLastUpdateMonth()
Returns the month of the last successful database update.
|
Instant |
getNextScheduledUpdate()
Returns the instant when the next scheduled update check will occur.
|
Path |
getOldConfigurationDirectory()
Returns the path to the old configuration directory awaiting cleanup.
|
RecoveryConfig |
getRecoveryConfig()
Returns the current recovery configuration.
|
String |
getRecoveryReason()
Returns the reason why recovery is recommended.
|
String |
getStatusDescription()
Returns the current status as a human-readable string for UI display.
|
boolean |
isClusterInstance()
Returns whether this server is running as a cluster instance.
|
boolean |
isConfigured()
Returns whether the manager has an active service configured.
|
boolean |
isEnabled()
Returns whether the service is enabled.
|
boolean |
isIPv4Ready()
Returns whether IPv4 lookups are available.
|
boolean |
isIPv6Ready()
Returns whether IPv6 lookups are available.
|
boolean |
isReady()
Returns whether the manager has a ready service.
|
boolean |
isReconfiguring()
Returns whether a reconfiguration is currently in progress.
|
boolean |
isRecoveryInProgress()
Returns whether recovery is currently in progress.
|
boolean |
isRecoveryRecommended()
Returns whether recovery mode is recommended.
|
boolean |
isShutdown()
Returns whether the manager has been shut down.
|
GeoLocation |
lookup(String ip)
Looks up geolocation information for an IP address.
|
void |
reconfigure(IP2LocationService.Builder builder)
Reconfigures the manager with a new service instance (blocking).
|
void |
reconfigureAsync(IP2LocationService.Builder builder,
IInitializationCallback callback)
Reconfigures the manager with a new service instance (non-blocking).
|
void |
removeStatusListener(IServiceStatusListener listener)
Removes a status listener.
|
void |
setClusterIndex(int index)
Sets the cluster index for this server instance.
|
void |
setLogger(IEventLogger logger)
Sets the event logger for operational messages.
|
void |
setRecoveryConfig(RecoveryConfig config)
Sets the recovery configuration.
|
void |
shutdown()
Shuts down the manager and all active services.
|
void |
triggerRecovery()
Triggers a manual recovery attempt.
|
void |
updateDatabasesNow()
Triggers an immediate database update.
|
public static IP2LocationManager getInstance()
This method is thread-safe and returns the same instance for all callers.
public void configure(IP2LocationService.Builder builder)
This method blocks until the service is fully initialized and ready
for lookups. For non-blocking initialization, use configureAsync(co.mindus.ip2location.IP2LocationService.Builder, co.mindus.ip2location.IInitializationCallback).
If a service is already configured, this method behaves like
reconfigure(IP2LocationService.Builder).
builder - The builder with configuration settingsIllegalStateException - if the manager has been shut downIllegalArgumentException - if builder is nullpublic void configureAsync(IP2LocationService.Builder builder, IInitializationCallback callback)
Returns immediately. The service initializes in the background.
Use awaitReady(long, TimeUnit) to wait for initialization,
or provide a callback to be notified when complete.
builder - The builder with configuration settingscallback - Optional callback for initialization result (may be null)IllegalStateException - if the manager has been shut downIllegalArgumentException - if builder is nullpublic void reconfigure(IP2LocationService.Builder builder)
This method performs a hot-swap of the service:
Lookups continue to work throughout this process without interruption.
If the new configuration uses the same directory as the old one, database files are automatically reused. The new instance will detect existing files and only download if they're outdated.
builder - The builder with new configuration settingsIllegalStateException - if manager is shut downIllegalArgumentException - if builder is nullpublic void reconfigureAsync(IP2LocationService.Builder builder, IInitializationCallback callback)
Returns immediately. The new service initializes in the background. The swap occurs only after the new instance is ready.
builder - The builder with new configuration settingscallback - Optional callback for reconfiguration result (may be null)IllegalStateException - if manager is shut downIllegalArgumentException - if builder is nullpublic GeoLocation lookup(String ip)
This method is thread-safe and can be called concurrently from multiple threads. It automatically uses the currently active service instance.
During reconfiguration, lookups continue to work without interruption. Requests that started before the swap will complete using the old instance, while new requests use the new instance.
ip - The IP address to look up (IPv4 or IPv6)GeoLocation containing the resultGeoLocation.isOk()public boolean awaitReady(long timeout,
TimeUnit unit)
throws InterruptedException
Blocks until a service is configured and ready, or timeout expires.
timeout - The maximum time to waitunit - The time unittrue if ready, false if timeout expiredInterruptedException - if interrupted while waitingpublic boolean isReady()
true if a service is configured and ready for lookupspublic boolean cleanupOldConfiguration()
This method should be called after reconfiguration when you want to remove database files from the previous configuration's directory. It only has effect if the new configuration uses a different directory than the old one.
Files cleaned up:
*.BIN files (IP2Location databases).ip2location-state.properties (download state)*.tmp files (incomplete downloads)The directory itself is removed if empty after cleanup.
true if cleanup was performed, false if nothing to clean uppublic Path getOldConfigurationDirectory()
null if nonepublic boolean isConfigured()
true if a service is configured (may still be initializing)public boolean isEnabled()
When the service is disabled, lookups return
GeoLocation.Status.SERVICE_DISABLED.
true if the service is enabled, false if disabled
or not configuredpublic boolean isIPv4Ready()
true if IPv4 database is loaded and readypublic boolean isIPv6Ready()
true if IPv6 database is loaded and readypublic YearMonth getLastUpdateMonth()
null if never updatedpublic boolean isReconfiguring()
A reconfiguration is in progress if an old instance is still draining.
true if an old instance is still drainingpublic int getActiveRequestCount()
This is the number of lookup requests currently being processed by the active instance.
public boolean isShutdown()
true if shutdown() has been calledpublic void setClusterIndex(int index)
This method is called by IP2LocationBootstrap.initialize(int)
during server startup. It should not be called from application code.
The cluster index determines the operating mode:
0 — Normal (unclustered) server. All configuration and
administration features are available.1 — First (primary) cluster server. Configuration changes
should be made through this instance's Admin UI. After changes,
the Cluster Controller restarts other instances to apply them.≥2 — Non-primary cluster server. The Admin UI should
restrict configuration editing on these instances, displaying
settings as read-only.index - The cluster index: 0 for unclustered, ≥1 for clustered instancesgetClusterIndex(),
isClusterInstance(),
IP2LocationBootstrap.initialize(int)public int getClusterIndex()
The cluster index identifies this JVM's role within a NetPhantom cluster:
0 — Normal (unclustered) server1 — First (primary) cluster server≥2 — Non-primary cluster serverUse isClusterInstance() for a simpler check of whether
this is a clustered server.
isClusterInstance(),
IP2LocationBootstrap.initialize(int)public boolean isClusterInstance()
A cluster instance runs in a separate JVM managed by the
NetPhantom Cluster Controller. Each instance has its own
database directory (with a #N suffix) and operates
independently for downloads, lookups, and recovery.
Equivalent to getClusterIndex() > 0.
true if this is a clustered server instancegetClusterIndex()public LocalDate getIPv4DatabaseDate()
The date reflects when the database was published by IP2Location, typically the 1st of the month.
null if no IPv4 database is loaded
or no service is configuredpublic LocalDate getIPv6DatabaseDate()
The date reflects when the database was published by IP2Location, typically the 1st of the month.
null if no IPv6 database is loaded,
IPv6 is not enabled, or no service is configuredpublic Instant getNextScheduledUpdate()
This reflects the scheduler's next planned execution, not necessarily when a download will occur (which depends on the current month and whether an update is needed).
null if
no service is configured or the scheduler is not runningpublic String getStatusDescription()
The status components are concatenated in order of importance, separated by ", " (comma and space). This includes both manager-level and service-level status components.
Manager-level components (in order of importance):
Service-level components (delegated from active service):
nullpublic boolean isRecoveryRecommended()
Recovery is recommended when the service encounters persistent errors that suggest corrupted database files or state. Conditions that trigger this recommendation:
When recovery is recommended, the UI should offer the user an option
to reconfigure with attemptRecovery() enabled.
if (manager.isRecoveryRecommended()) {
String reason = manager.getRecoveryReason();
boolean userConfirmed = showConfirmation(
"Recovery Recommended",
reason + "\n\nAttempt recovery?");
if (userConfirmed) {
manager.reconfigure(new IP2LocationService.Builder()
.downloadToken(token)
.attemptRecovery() // Clears corrupted files
.build());
}
}
true if recovery is recommendedgetRecoveryReason(),
clearRecoveryRecommendation(),
IP2LocationService.Builder.attemptRecovery()public String getRecoveryReason()
This provides a human-readable explanation suitable for display in a UI dialog or log message.
null if recovery is not recommendedisRecoveryRecommended()public int getConsecutiveInitFailures()
This counter is incremented each time configuration or reconfiguration fails, and reset to 0 on successful initialization.
public int getConsecutiveLookupErrors()
This counter is incremented when lookups return DB_ERROR status,
and reset to 0 on any successful lookup.
public void clearRecoveryRecommendation()
Call this after the user dismisses the recovery recommendation, or after a successful recovery. The recommendation will be re-raised if errors continue.
public void addStatusListener(IServiceStatusListener listener)
The listener will be notified of events such as initialization, downloads, database updates, and reconfiguration.
This method is thread-safe and idempotent - adding the same listener multiple times has no additional effect.
manager.addStatusListener(new IServiceStatusListener() {
@Override
public void onDatabaseUpdated(boolean ipv4, boolean ipv6) {
System.out.println("Databases updated!");
}
});
listener - The listener to add (must not be null)NullPointerException - if listener is nullremoveStatusListener(IServiceStatusListener),
IServiceStatusListenerpublic void removeStatusListener(IServiceStatusListener listener)
The listener will no longer receive events after this call returns.
This method is thread-safe and idempotent - removing a listener that is not registered has no effect.
listener - The listener to remove (must not be null)NullPointerException - if listener is nulladdStatusListener(IServiceStatusListener)public void setRecoveryConfig(RecoveryConfig config)
This configuration controls how the manager responds to errors and when/whether automatic recovery is triggered.
manager.setRecoveryConfig(RecoveryConfig.builder()
.recoveryAction(RecoveryConfig.RecoveryAction.AUTOMATIC)
.initFailureThreshold(3)
.lookupErrorThreshold(20)
.recoverOnDownloadFailure(true)
.recoverOnCorruption(true)
.maxAutoRecoveryAttempts(3)
.autoRecoveryCooldownMinutes(60)
.build());
config - The recovery configuration (must not be null)NullPointerException - if config is nullRecoveryConfig,
getRecoveryConfig()public RecoveryConfig getRecoveryConfig()
setRecoveryConfig(RecoveryConfig)public boolean canTriggerRecovery()
Returns true if all of the following conditions are met:
true if triggerRecovery() can be calledtriggerRecovery()public boolean isRecoveryInProgress()
true if recovery is in progresspublic int getAutoRecoveryAttempts()
This counter is incremented each time automatic recovery is triggered and reset when recovery succeeds or when manually cleared.
public void triggerRecovery()
This clears the database directory and re-downloads fresh databases.
The operation runs asynchronously. Use canTriggerRecovery()
to check if this method can be called.
Recovery involves:
attemptRecovery() enabled
if (manager.canTriggerRecovery()) {
manager.triggerRecovery();
}
IllegalStateException - if recovery cannot be triggeredcanTriggerRecovery(),
IServiceStatusListener.onRecoveryCompleted(boolean, Throwable)public boolean canUpdateDatabasesNow()
Returns true if all of the following conditions are met:
Note: This does not check if a download is already in progress.
Calling updateDatabasesNow() while a download is in progress
is safe but will queue another download.
true if updateDatabasesNow() can be calledupdateDatabasesNow()public void updateDatabasesNow()
This bypasses the normal scheduling logic and attempts to download the latest databases immediately. The operation runs asynchronously.
Use canUpdateDatabasesNow() to check if this method can be
called successfully.
if (manager.canUpdateDatabasesNow()) {
manager.updateDatabasesNow();
// Status will change to "Downloading IPv4" etc.
}
IllegalStateException - if no service is configured or service
is not in a state that allows updatescanUpdateDatabasesNow()public void setLogger(IEventLogger logger)
This logger is used for manager-level events. Each service instance has its own logger configured via its builder.
logger - The logger implementation, or null for console loggingpublic void shutdown()
This method should be called during application shutdown. It:
After calling this method, the manager cannot be reconfigured. Note: As this is a singleton, shutting down affects the entire application.
Phantom® and NetPhantom® are registered trademarks of Mindus SARL.
© 2026 Mindus SARL. All rights reserved.