Prerequisites
- Java 17 or later (JDK 17+)
- A Maven or Gradle project
- An ip-api.io API key — get one free
Add the dependency
Add the official io.ip-api:ipapi artifact from Maven Central with Maven or
Gradle.
<!-- Maven: pom.xml -->
<dependency>
<groupId>io.ip-api</groupId>
<artifactId>ipapi</artifactId>
<version>1.0.0</version>
</dependency> // Gradle: build.gradle.kts
implementation("io.ip-api:ipapi:1.0.0") Build the client
Build one IpApiClient with your API key using the builder. Read the key
from an environment variable — never hardcode it. The API rejects keyless requests with
401.
import io.ipapi.IpApiClient;
IpApiClient client = IpApiClient.builder()
.apiKey(System.getenv("IP_API_IO_KEY"))
.build();
// ... use the client ...
IpApiClient wraps a single HttpClient and is thread-safe —
build one instance (e.g. a Spring @Bean) and reuse it instead of creating
one per request.
Look up an IP address
Call lookup(ip) for any IPv4 or IPv6 address, or lookup() to
resolve the caller's own IP. Responses are record types; nullable fields
are reference types that may be null.
IpInfo info = client.lookup("8.8.8.8");
System.out.println(info.ip()); // "8.8.8.8"
System.out.println(info.isp()); // "Google LLC"
System.out.println(info.location().country()); // "United States"
System.out.println(info.suspiciousFactors().isDatacenter()); // true
// Resolve the caller's own IP:
IpInfo me = client.lookup();
System.out.println(me.ip()); country()
and city() may return null for private ranges or unrecognized
addresses — null-check (or use Optional.ofNullable(...)) before use. Boolean
flags such as info.suspiciousFactors().isVpn() are primitive
boolean.
Detect VPN, proxy, Tor & batch lookups
The suspiciousFactors() record carries seven boolean fields on every
lookup — this is what sets ip-api.io apart from plain geolocation APIs. You get
security intelligence in the same call, at no extra cost.
IpInfo info = client.lookup("185.220.101.45"); // example Tor exit node
var f = info.suspiciousFactors();
if (f.isVpn() || f.isProxy() || f.isTorNode())
System.out.println("Anonymized traffic — consider a CAPTCHA or block");
if (f.isDatacenter())
System.out.println("Cloud/datacenter IP — likely automated");
if (f.isThreat())
System.out.println("Active threat signal — block this request");
boolean risky = f.isVpn() || f.isProxy() || f.isTorNode() || f.isThreat();
Need to check many IPs at once? lookupBatch takes up to 100 addresses in a
single request:
var batch = client.lookupBatch(List.of("8.8.8.8", "1.1.1.1", "9.9.9.9"));
System.out.println(batch.totalProcessed() + " " + batch.successfulLookups());
batch.results().forEach((ip, result) ->
System.out.println(ip + " " + result.suspiciousFactors().isVpn())); client.riskScore(ip) for a combined
fraud risk score from
0 to 100.
Production-ready error handling
Methods throw unchecked IpApiException subtypes and the client
never retries on its own, so on a rate limit
RateLimitException.getReset() tells you exactly when to try again.
import io.ipapi.exception.*;
try {
IpInfo info = client.lookup("8.8.8.8");
System.out.println(info.ip());
} catch (RateLimitException e) {
System.out.println("Rate limited. Remaining: " + e.getRemaining()
+ ". Resets at " + e.getReset());
} catch (AuthenticationException e) {
System.out.println("Invalid or missing API key — get one free at https://ip-api.io");
} catch (IpApiException e) {
// Invalid request, server error, or transport failure
System.out.println("ip-api.io error: " + e.getMessage());
} RateLimitException.getReset() (a Unix timestamp) to schedule the next
attempt. Because the exceptions are unchecked, you opt into handling them where it
makes sense rather than wrapping every call.
In a Spring Boot controller, run the lookup server-side and pass the real client IP
from the X-Forwarded-For header:
@GetMapping("/check")
public Map<String, Object> check(HttpServletRequest request) {
String header = request.getHeader("X-Forwarded-For");
String clientIp = header != null
? header.split(",")[0].trim()
: request.getRemoteAddr();
try {
IpInfo info = client.lookup(clientIp);
return Map.of(
"ip", info.ip(),
"country", info.location().country(),
"isVpn", info.suspiciousFactors().isVpn());
} catch (IpApiException e) {
// Treat ip-api.io as a non-critical dependency
return Map.of("ip", clientIp);
}
}