Implementing High-Performance IP Geolocation in Spring Boot with Ip2region

Ip2region Framework Overview

The ip2region v2.0 library serves as an offline IP address定位 framework and data management solution. It achieves microsecond-level query performance and provides xdb data generation and query client implementations for various mainstream programming languages.

Core Capabilities

Standardized Data Structure

The region information for every IP data segment adheres to a fixed format: Country|Region|Province|City|ISP. Data for China is predominantly precise to the city level, whereas data for other nations may only resolve to the country level, with subsequent fields defaulting to zero.

Data Optimization

The xdb generation process automatically handles data deduplication and compression. For the default global IP dataset, the resulting ip2region.xdb file size is approximately 11 MiB. This size may vary depending on the level of detail in the source data.

Query Acceleration

Querying solely based on the xdb file yields response times in the ten-microsecond range. Performance can be further optimized using two memory-caching strategies:

  • vIndex Caching: Allocates a fixed 512 KiB memory buffer to cache vector index data. This reduces disk I/O operations by one and stabilizes query efficiency between 10 to 20 microseconds.
  • Full File Caching: Loads the entire xdb file into memory. While memory consumption equals the file size, this eliminates all disk I/O, maintaining microsecond-level query speeds.

Extensible Data Management

The v2.0 xdb format supports billions of IP data segments. Region information is fully customizable, allowing the integration of specific business data such as GPS coordinates, UN geocodes, or postal codes. This flexibility allows developers to use ip2region as a framework for managing proprietary IP geolocation data.

Spring Boot Integration

1. Resource Acquisition

Clone the repository and locate the database file.

git clone https://github.com/lionsoul2014/ip2region.git

Copy the ip2region.xdb file from the data/ directory into your project's src/main/resources folder.

2. Maven Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
<!-- IP Geolocation Library -->
<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>2.7.0</version>
</dependency>
<!-- User Agent Parsing -->
<dependency>
    <groupId>eu.bitwalker</groupId>
    <artifactId>UserAgentUtils</artifactId>
    <version>1.21</version>
</dependency>

3. Implementation Logic

The following utility class handles database initialization via a static block, ensuring the data is loaded into memory to resolve issues with accessing files within JAR packages. It also includes logic to extract the real client IP from proxy headers.

package com.example.geo.util;

import eu.bitwalker.useragentutils.UserAgent;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class NetworkGeoResolver {

    private static final Logger logger = LoggerFactory.getLogger(NetworkGeoResolver.class);
    private static final String LOCAL_IPV4 = "127.0.0.1";
    private static Searcher locationSearcher;

    static {
        try {
            // Load the xdb file into memory to avoid IO issues in JAR packaging
            InputStream dbStream = NetworkGeoResolver.class.getResourceAsStream("/ip2region/ip2region.xdb");
            byte[] binaryData = FileCopyUtils.copyToByteArray(dbStream);
            locationSearcher = Searcher.newWithBuffer(binaryData);
            logger.info("IP2Region database loaded into memory successfully.");
        } catch (IOException e) {
            logger.error("Failed to initialize IP searcher", e);
            throw new RuntimeException("Initialization failed", e);
        }
    }

    public static String determineIpAddress(HttpServletRequest request) {
        String clientIp = null;
        
        // Check Kubernetes or specific proxy headers first
        clientIp = checkHeader(request, "X-Original-Forwarded-For");
        if (clientIp != null) return sanitizeIp(clientIp);

        // Standard proxy headers
        clientIp = checkHeader(request, "X-Forwarded-For");
        if (clientIp != null) return sanitizeIp(clientIp);
        
        clientIp = checkHeader(request, "Proxy-Client-IP");
        if (clientIp != null) return sanitizeIp(clientIp);
        
        clientIp = checkHeader(request, "WL-Proxy-Client-IP");
        if (clientIp != null) return sanitizeIp(clientIp);

        // Fallback to remote address
        clientIp = request.getRemoteAddr();
        if (LOCAL_IPV4.equals(clientIp)) {
            try {
                clientIp = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                logger.error("Error resolving local host", e);
            }
        }
        
        // Handle IPv6 loopback mapping
        return "0:0:0:0:0:0:0:1".equals(clientIp) ? LOCAL_IPV4 : clientIp;
    }

    private static String checkHeader(HttpServletRequest request, String headerName) {
        String value = request.getHeader(headerName);
        return (StringUtils.isEmpty(value) || "unknown".equalsIgnoreCase(value)) ? null : value;
    }

    private static String sanitizeIp(String ipList) {
        if (ipList != null && ipList.length() > 15 && ipList.indexOf(",") > 0) {
            return ipList.substring(0, ipList.indexOf(","));
        }
        return ipList;
    }

    public static UserAgent parseUserAgent(HttpServletRequest request) {
        return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
    }

    public static String fetchLocationData(String ipAddress) {
        if (locationSearcher == null) {
            logger.error("Searcher not initialized");
            return null;
        }
        try {
            String rawInfo = locationSearcher.search(ipAddress);
            if (StringUtils.isNotBlank(rawInfo)) {
                // Clean up null placeholders (0)
                return rawInfo.replace("|0", "").replace("0|", "");
            }
        } catch (Exception e) {
            logger.error("Failed to search location for IP: {}", ipAddress, e);
        }
        return null;
    }

    public static String getHostname() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return "Unknown Host";
        }
    }
}

4. Verification

The following test case demonstrates how to retrieve geolocation data for a specific IP address.

package com.example.geo;

import com.example.geo.util.NetworkGeoResolver;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class GeoLocationTests {

    @Test
    public void verifyIpLookup() {
        // Replace with a valid public IP for testing
        String location = NetworkGeoResolver.fetchLocationData("8.8.8.8");
        System.out.println("Resolved Location: " + location);
    }
}

Tags: SpringBoot java Ip2region geolocation network

Posted on Tue, 07 Jul 2026 16:55:10 +0000 by raimis100