Class IP2LocationService

java.lang.Object
co.mindus.ip2location.IP2LocationService
All Implemented Interfaces:
AutoCloseable

public class IP2LocationService extends Object implements AutoCloseable
High-level service for IP geolocation lookups with automatic database updates.

This service provides a complete solution for IP geolocation including:

  • Automatic monthly database downloads from IP2Location
  • Support for both IPv4 and IPv6 lookups
  • Thread-safe concurrent lookups
  • Hot-reload of databases without service restart
  • Configurable memory-mapped file support
  • Proxy support via system ProxySelector
  • Asynchronous initialization with callbacks
  • Custom thread factory for virtual thread support (Java 21+)
  • Parallel download of IPv4 and IPv6 databases
  • Recovery mode for corrupted state or databases
  • Disabled mode for dev/test environments or feature flags
  • Human-readable status descriptions for UI display

Quick Start (Blocking)

IP2LocationService service = new IP2LocationService.Builder()
    .downloadToken("YOUR_TOKEN")
    .database(Database.DB11)
    .includeIPv6(true)
    .directory(Paths.get("/var/lib/ip2location"))
    .build();  // Blocks until databases are loaded

GeoLocation loc = service.lookup("8.8.8.8");
if (loc.isOk()) {
    System.out.println("Country: " + loc.getCountryCode());
    System.out.println("Location: " + loc.getLocationDescription());
}

// Display service status in UI
statusLabel.setText(service.getStatusDescription());
// "IPv4+IPv6, IPv4 ready, IPv6 ready"

service.dispose();

Async Initialization

IP2LocationService service = new IP2LocationService.Builder()
    .downloadToken("YOUR_TOKEN")
    .database(Database.DB11)
    .buildAsync((svc, success, error) -> {
        if (success) {
            System.out.println("Service ready!");
        } else {
            System.err.println("Init failed: " + error);
        }
    });

// Service returned immediately, initialization runs in background
// Wait for initialization if needed:
boolean ready = service.awaitReady(30, TimeUnit.SECONDS);

Recovery Mode

If the database or state files become corrupted, use recovery mode to clear everything and start fresh:

IP2LocationService service = new IP2LocationService.Builder()
    .downloadToken("YOUR_TOKEN")
    .database(Database.DB11)
    .directory(Paths.get("/var/lib/ip2location"))
    .attemptRecovery()  // Clears directory before initialization
    .build();

For manual recovery without creating a new service:

// Clear directory manually (service must be disposed first!)
Path dir = Paths.get("/var/lib/ip2location");
int deleted = IP2LocationService.clearDirectory(dir, IEventLogger.nullLogger());
System.out.println("Deleted " + deleted + " files");

Disabled Mode

The service can be started disabled for environments where geolocation is not needed:

boolean useGeoLocation = Boolean.parseBoolean(
    System.getProperty("app.geolocation.enabled", "true"));

IP2LocationService service = new IP2LocationService.Builder()
    .downloadToken("YOUR_TOKEN")
    .enabled(useGeoLocation)  // Disable via configuration
    .build();

if (service.isEnabled()) {
    GeoLocation loc = service.lookup("8.8.8.8");
}

Threads

Background work (initialization, downloads, the update scheduler) runs on virtual threads by default (IThreadFactory.virtualFactory()); a host that manages its own threads sets another factory:

IP2LocationService service = new IP2LocationService.Builder()
    .downloadToken("YOUR_TOKEN")
    .threadFactory(IThreadFactory.defaultFactory())
    .buildAsync();

Database Update Schedule

IP2Location LITE databases are updated on the 1st of each calendar month. This service follows IP2Location's recommendation to download during days 1-7 of each month. The service also forces a download on startup if the existing database is from a previous month.

IPv6 Support

When IP2LocationService.Builder.includeIPv6(boolean) is set to true:

  • Both IPv4 and IPv6 databases are downloaded (in parallel for speed)
  • IPv4 lookups use the smaller IPv4-only database for efficiency
  • IPv6 lookups use the IPv6 database (which also contains IPv4 ranges)

Memory-Mapped Files

The service supports two database access modes:

  • Memory-mapped (useMemoryMappedFile=true): Uses less heap memory, OS manages page caching. Best for production servers with adequate RAM.
  • Standard (useMemoryMappedFile=false, default): More predictable memory behavior. Better for containers or memory-constrained environments.

Reconfiguration

This service is immutable after construction. To change configuration:

  1. Call dispose() on the existing instance
  2. Create a new instance with IP2LocationService.Builder

For seamless reconfiguration without downtime, use IP2LocationManager.

Thread Safety

This class is thread-safe. Multiple threads can safely call lookup(String) concurrently. Database reloads are performed atomically using read-write locks.

The builder defaults to virtual threads for all background work.

Author:
Christopher Mindus
See Also:
  • Method Details

    • clearDirectory

      public static int clearDirectory(Path directory, IEventLogger logger) throws IOException
      Clears an IP2Location directory, removing all database and state files.

      This is a recovery utility that can be used when the database or state files become corrupted. It deletes:

      • All *.BIN and *.bin database files
      • The state file (.ip2location-state.properties)
      • All *.tmp temporary files
      • All *.zip download files

      Important: All IP2LocationService instances using this directory must be disposed before calling this method. Otherwise, the service may hold file locks that prevent deletion, or may become unstable.

      Usage Example

      // First, dispose the service
      service.dispose();
      
      // Clear the directory
      Path dir = Paths.get("/var/lib/ip2location");
      int deleted = IP2LocationService.clearDirectory(dir, IEventLogger.nullLogger());
      System.out.println("Deleted " + deleted + " files");
      
      // Create a new service (will download fresh databases)
      service = new IP2LocationService.Builder()
          .downloadToken("YOUR_TOKEN")
          .directory(dir)
          .build();
      
      Parameters:
      directory - The directory to clear
      logger - Logger for reporting progress (may be null for silent operation)
      Returns:
      The number of files deleted
      Throws:
      IOException - if the directory cannot be accessed or files cannot be deleted
    • hasValidDatabases

      public static boolean hasValidDatabases(Path directory, boolean requireIPv6)
      Checks if an IP2Location directory contains valid database files.

      This method can be used to determine if recovery might be needed before creating a service instance.

      Parameters:
      directory - The directory to check
      requireIPv6 - Whether to require both IPv4 and IPv6 databases
      Returns:
      true if the directory contains the required database files
    • dispose

      public void dispose()
      Disposes of the service, releasing all resources.

      This method:

      1. Cancels all scheduled update tasks
      2. Closes all loaded database readers
      3. Releases any memory-mapped file mappings

      After calling this method, the service cannot be restarted. This method is idempotent.

    • close

      public void close()
      Alias for dispose() to support try-with-resources.
      Specified by:
      close in interface AutoCloseable
    • isReady

      public boolean isReady()
      Returns whether the service is ready to handle lookups.

      A service is ready when at least one database has been loaded. This method returns immediately without blocking.

      Returns:
      true if at least one database is loaded
    • awaitReady

      public boolean awaitReady(long timeout, TimeUnit unit) throws InterruptedException
      Waits for the service to become ready.

      Blocks until initialization completes or the timeout expires.

      Parameters:
      timeout - The maximum time to wait
      unit - The time unit
      Returns:
      true if the service is ready, false if timeout expired
      Throws:
      InterruptedException - if the current thread was interrupted
    • getInitializationError

      public Throwable getInitializationError()
      Returns the error that occurred during initialization.
      Returns:
      The initialization error, or null if successful
    • lookup

      public GeoLocation lookup(String ip)
      Looks up geolocation information for an IP address.

      This method automatically detects whether the IP is IPv4 or IPv6 and routes to the appropriate database.

      Parameters:
      ip - The IP address to look up (e.g., "8.8.8.8" or "2001:4860:4860::8888")
      Returns:
      A GeoLocation containing the result (check GeoLocation.isOk())
    • isEnabled

      public boolean isEnabled()
      Returns whether the service is enabled.

      When disabled, the service does not download or load databases, does not start the update scheduler, and lookups return STATUS_SERVICE_DISABLED.

      A disabled service consumes minimal resources and can be used as a placeholder when geolocation is not needed in certain environments (e.g., development, testing).

      Returns:
      true if the service is enabled, false if disabled
      See Also:
    • isRunning

      public boolean isRunning()
      Returns whether the service is currently running.
      Returns:
      true if started and not disposed
    • isIPv4Ready

      public boolean isIPv4Ready()
      Returns whether an IPv4 database is loaded and ready.
      Returns:
      true if IPv4 lookups are available
    • isIPv6Ready

      public boolean isIPv6Ready()
      Returns whether an IPv6 database is loaded and ready.
      Returns:
      true if IPv6 lookups are available
    • getLastUpdateMonth

      public YearMonth getLastUpdateMonth()
      Returns the month of the last successful database update.
      Returns:
      The year-month of last update, or null if never updated
    • isUsingMemoryMappedFile

      public boolean isUsingMemoryMappedFile()
      Returns whether memory-mapped file mode is enabled.
      Returns:
      true if using memory-mapped files
    • getDirectory

      public Path getDirectory()
      Returns the configured storage directory.
      Returns:
      Path to the database storage directory
    • getDatabase

      public Database getDatabase()
      Returns the configured database type.
      Returns:
      The database type being used
    • isIPv6Enabled

      public boolean isIPv6Enabled()
      Returns whether IPv6 support is enabled.
      Returns:
      true if IPv6 lookups are enabled
    • getIPv4DatabaseDate

      public LocalDate getIPv4DatabaseDate()
      Returns the date of the currently loaded IPv4 database.

      The date reflects when the database was published by IP2Location, typically the 1st of the month.

      Returns:
      The database date, or null if no IPv4 database is loaded
    • getIPv6DatabaseDate

      public LocalDate getIPv6DatabaseDate()
      Returns the date of the currently loaded IPv6 database.

      The date reflects when the database was published by IP2Location, typically the 1st of the month.

      Returns:
      The database date, or null if no IPv6 database is loaded or IPv6 support is not enabled
    • getNextScheduledUpdate

      public Instant getNextScheduledUpdate()
      Returns the instant when the next scheduled update check will occur.

      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).

      Returns:
      The instant of the next scheduled check, or null if the scheduler is not running (service disabled, not started, or disposed)
    • getStatusDescription

      public String getStatusDescription()
      Returns the current status as a human-readable string for UI display.

      The status components are concatenated in order of importance, separated by ", " (comma and space). Possible components include:

      • Disposed - Service has been disposed
      • Disabled - Service is disabled
      • Initializing - Service is starting up
      • IPv4 only - Only IPv4 lookups enabled
      • IPv4+IPv6 - Both IPv4 and IPv6 lookups enabled
      • Downloading IPv4 - IPv4 database download in progress
      • Downloading IPv6 - IPv6 database download in progress
      • IPv4 ready - IPv4 database loaded
      • IPv6 ready - IPv6 database loaded
      • IPv4 not loaded - IPv4 database not available
      • IPv6 not loaded - IPv6 database not available

      Example outputs:

      • "Disabled"
      • "IPv4 only, IPv4 ready"
      • "IPv4+IPv6, Downloading IPv4, Downloading IPv6"
      • "IPv4+IPv6, IPv4 ready, IPv6 ready"
      • "Disposed"
      Returns:
      A comma-separated status string, never null
    • setDownloadCallback

      public void setDownloadCallback(IInitializationCallback callback)
      Sets a callback invoked after each download attempt (forced or scheduled).
      Parameters:
      callback - The callback, or null to remove
    • setDatabaseLoadCallback

      public void setDatabaseLoadCallback(BiConsumer<LocalDate, Boolean> callback)
      Sets a callback invoked when a database file is loaded into memory.
      Parameters:
      callback - The callback receiving (date, isIPv6), or null to remove
    • forceUpdate

      public void forceUpdate()
      Forces an immediate database update check and download.

      This bypasses the normal scheduling logic and attempts to download the latest database immediately. The operation runs asynchronously.

      Throws:
      IllegalStateException - if the service is not running