Retrieve Client IP and Geo-Location in Java with HttpServletRequest & ip2region

This article explains how to obtain a user's IP address from an HttpServletRequest, handle proxy headers, and use the ip2region library to map the IP to province, city, or country, covering setup, Maven integration, code examples, caching strategies, and performance testing.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Retrieve Client IP and Geo-Location in Java with HttpServletRequest & ip2region

Background

Recently many platforms such as Weibo, Toutiao, Tencent, Douyin, Zhihu, Kuaishou, and Xiaohongshu have added a feature that displays the network user's IP address location, showing the country for overseas users and the province for domestic users, and this display cannot be turned off.

HttpServletRequest Get IP

The following steps show how to obtain the IP address and its location in Java:

Use the HttpServletRequest object to get the user's IP address.

Map the IP address to the corresponding province and city.

Create a utility class to extract the IP from request headers for each request.

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
/**
 * Common utility for obtaining client information
 */
public class NetworkUtil {
    /**
     * Get IP address
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // Localhost handling
        if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) {
            try {
                InetAddress inet = InetAddress.getLocalHost();
                ip = inet.getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        // If multiple proxies, take the first IP
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 15) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }
    /**
     * Get MAC address
     */
    public static String getMacAddress() throws Exception {
        byte[] macAddressBytes = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < macAddressBytes.length; i++) {
            if (i != 0) {
                sb.append("-");
            }
            String s = Integer.toHexString(macAddressBytes[i] & 0xFF);
            sb.append(s.length() == 1 ? "0" + s : s);
        }
        return sb.toString().trim().toUpperCase();
    }
}

This method extracts the user's IP address from request headers.

Previously I used the Taobao IP library (https://ip.taobao.com/) to map IP to province and city, but the service has been discontinued.

Ip2region

Ip2region is an open‑source offline IP location library and data management framework. Version 2.0 provides microsecond‑level query performance and supports many programming languages.

https://github.com/lionsoul2014/ip2region

Key characteristics:

99.9% accuracy by aggregating data from major providers (Taobao, GeoIP, CZ88).

Supports multiple client languages: Java, C#, PHP, C, Python, Node.js, Go, Rust, Lua, Nginx, etc.

Ip2region V2.0 Features

Standardized data format : region field format is Country|Region|Province|City|ISP. Chinese data is usually city‑level; other countries may only have country.

Deduplication and compression : The xdb generator removes duplicate entries and compresses data; the default database is about 11 MiB.

Ultra‑fast query response : Pure file‑based queries achieve ~10 µs latency. Two memory‑acceleration options are available:

vIndex cache (≈512 KiB) stores vector index to reduce one disk I/O, keeping average latency 10‑20 µs.

Full xdb file cache loads the entire database into memory, eliminating disk I/O and achieving microsecond‑level latency.

Scalable data rows : v2.0 supports billions of IP segments, and the region field can be customized to include additional business data such as GPS or postal codes.

ip2region xdb Java query client implementation

1. Maven dependency

<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>2.6.4</version>
</dependency>

2. File‑only query

import org.lionsoul.ip2region.xdb.Searcher;
import java.io.*;
import java.util.concurrent.TimeUnit;

public class SearcherTest {
    public static void main(String[] args) {
        // 1. Create searcher
        String dbPath = "ip2region.xdb file path";
        Searcher searcher = null;
        try {
            searcher = Searcher.newWithFileOnly(dbPath);
        } catch (IOException e) {
            System.out.printf("failed to create searcher with `%s`: %s
", dbPath, e);
            return;
        }
        // 2. Query
        try {
            String ip = "1.2.3.4";
            long sTime = System.nanoTime();
            String region = searcher.search(ip);
            long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
            System.out.printf("{region: %s, ioCount: %d, took: %d μs}
", region, searcher.getIOCount(), cost);
        } catch (Exception e) {
            System.out.printf("failed to search(%s): %s
", ip, e);
        }
        // Note: each thread should create its own Searcher instance.
    }
}

3. VectorIndex cache

import org.lionsoul.ip2region.xdb.Searcher;
import java.io.*;
import java.util.concurrent.TimeUnit;

public class SearcherTest {
    public static void main(String[] args) {
        String dbPath = "ip2region.xdb file path";
        // Load VectorIndex once
        byte[] vIndex;
        try {
            vIndex = Searcher.loadVectorIndexFromFile(dbPath);
        } catch (Exception e) {
            System.out.printf("failed to load vector index from `%s`: %s
", dbPath, e);
            return;
        }
        // Create searcher with cached VectorIndex
        Searcher searcher;
        try {
            searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
        } catch (Exception e) {
            System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s
", dbPath, e);
            return;
        }
        // Query
        try {
            String ip = "1.2.3.4";
            long sTime = System.nanoTime();
            String region = searcher.search(ip);
            long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
            System.out.printf("{region: %s, ioCount: %d, took: %d μs}
", region, searcher.getIOCount(), cost);
        } catch (Exception e) {
            System.out.printf("failed to search(%s): %s
", ip, e);
        }
        // Each thread should use its own Searcher instance, sharing the global vIndex.
    }
}

4. Full xdb content cache

Load the entire xdb file into memory and create a Searcher that can be safely shared across threads for concurrent queries.

IDEA testing

When the IP belongs to China, the province is displayed; otherwise only the country is shown. The following screenshots illustrate the test setup and results.

Running the provided jar with the search command allows interactive IP queries, while the bench command evaluates performance with a source IP list.

# cd to java binding root
cd binding/java/
mvn compile package
java -jar target/ip2region-2.6.0.jar search --db=../../data/ip2region.xdb
ip2region>> 1.2.3.4
{region: 美国|0|华盛顿|0|谷歌, ioCount: 7, took: 82 μs}
java -jar target/ip2region-2.6.0.jar bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
Bench finished, {cachePolicy: vectorIndex, total: 3417955, took: 8s, cost: 2 μs/op}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaHttpServletRequestgeolocationIP addressip2region
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.