Java SDK

ip-api.io Java SDK
io.ip-api:ipapi

io.ip-api:ipapi is the official client for Java — built on the JDK's java.net.http.HttpClient with immutable record responses. One IpApiClient wraps IP geolocation, VPN/proxy/Tor detection, email validation, and fraud risk scoring, with a builder, typed exceptions, and batch helpers, so you write the call, not the plumbing.

Location
Mountain View, US
Threat signals
No VPN or proxy
Runtime
Java 17+ · records
client.lookup("8.8.8.8")
{
"ip": "8.8.8.8",
"isp": "Google LLC",
"location": {
"country": "United States",
"city": "Mountain View",
"timezone": "America/Los_Angeles"
},
"suspicious_factors": {
"is_vpn": false,
"is_datacenter": true
}
}

What you'll build

  • Add the dependency and build one client
  • Look up any IP — or the caller's IP — with one call
  • Detect VPN, proxy, Tor, and threats; batch up to 100 IPs
  • Catch the typed exceptions the production-ready way
Trusted by thousands of businesses
Fast JSON API responses
Real-time validation
Simple integration, SDKs & examples

Prerequisites

  • Java 17 or later (JDK 17+)
  • A Maven or Gradle project
  • An ip-api.io API key — get one free
1

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")
2

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.

3

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());
Nullable reference types: location fields like 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.
4

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()));
Tip: want a single block / review / allow decision instead of raw flags? Use client.riskScore(ip) for a combined fraud risk score from 0 to 100.
5

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());
}
No automatic retries: wrap calls in your own retry/backoff logic and use 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);
    }
}
Reference

Response & method reference

lookup returns a typed IpInfo record with three parts: the top-level ip()/isp()/asn(), a suspiciousFactors() record, and a location() record. Nullable fields are reference types that may be null for private ranges or unrecognized addresses.

client .lookup("78.55.53.58")
{
  "ip": "78.55.53.58",
  "isp": "Deutsche Telekom AG",
  "asn": "AS3320",
  "suspicious_factors": {
    "is_proxy":      false,
    "is_tor_node":   false,
    "is_spam":       false,
    "is_crawler":    false,
    "is_datacenter": false,
    "is_vpn":        false,
    "is_threat":     false
  },
  "location": {
    "country":           "Germany",
    "country_code":      "DE",
    "city":              "Berlin",
    "latitude":          52.5694,
    "longitude":         13.3753,
    "zip":               "13409",
    "timezone":          "Europe/Berlin",
    "local_time":        "2024-05-20T22:16:52+02:00",
    "local_time_unix":   1716236212,
    "is_daylight_savings": true
  }
}

suspiciousFactors() — security signals (all boolean)

AccessorDescription
isVpn()VPN service, corporate gateway, or self-hosted VPN detected
isProxy()HTTP, HTTPS, or SOCKS proxy (~99.5% accuracy)
isTorNode()Tor exit node, relay, or bridge (updated in real time)
isDatacenter()Cloud provider (AWS, GCP, Azure), VPS, or hosting facility
isThreat()Active security threat — malware C&C, botnet, or DDoS source
isSpam()Associated with spam, phishing, or malware email campaigns
isCrawler()Known web crawler, scraper, or bot

location() — geographic and timezone data

AccessorTypeDescription
country()StringFull country name in English (ISO 3166-1)
countryCode()StringISO 3166-1 alpha-2 code (e.g. "DE", "US")
city()StringCity or municipality name (85–95% accuracy)
latitude()DoubleDecimal degrees, WGS84 — ~50 km median accuracy radius
longitude()DoubleDecimal degrees, WGS84 — ~50 km median accuracy radius
zip()StringPostal or ZIP code in country-specific format
timezone()StringIANA timezone identifier (e.g. "America/Los_Angeles")
localTime()StringCurrent local time in ISO 8601 with UTC offset
localTimeUnix()LongUnix timestamp (seconds) in local timezone
isDaylightSavings()BooleanTrue if currently observing DST; null if not applicable

Client methods

MethodReturnsWhat it does
lookup() / lookup(ip)IpInfoGeolocate the caller's IP, or a specific IP
lookupBatch(ips)BatchIpLookupGeolocate up to 100 IPs in one call
riskScore() / riskScore(ip)RiskScoreFraud risk score (0–100) for an IP
torCheck(ip)TorDetectionCheck whether an IP is a Tor node
emailInfo(email)EmailInfoBasic email validation (syntax, MX, disposable)
validateEmail(email)AdvancedEmailValidationFull validation incl. SMTP deliverability
asn(ip)AsnLookupAutonomous System Number lookup
whois(domain)WhoisWHOIS domain registration info
domainAge(domain)DomainAgeDomain registration date and age in days
rateLimit()RateLimitInfoRemaining quota and plan limits
Full surface: the client also exposes validateEmailBatch, emailRiskScore, ipReputation, reverseDns, forwardDns, mxRecords, domainAgeBatch, and usageSummary. See the README and per-feature guides.
FAQ

Frequently asked questions

Common questions about the ip-api.io Java SDK.

Is the io.ip-api:ipapi library official?

Yes. io.ip-api:ipapi is the official Java client for ip-api.io, maintained by the ip-api.io team and published to Maven Central from github.com/ip-api-io/ipapi-java. It is MIT licensed and not affiliated with ip-api.com or ipapi.com.

How do I install the ip-api.io Java SDK?

Add the io.ip-api:ipapi dependency to Maven or Gradle, then build a client with the builder:

// implementation("io.ip-api:ipapi:1.0.0")
import io.ipapi.IpApiClient;

IpApiClient client = IpApiClient.builder()
        .apiKey(System.getenv("IP_API_IO_KEY"))
        .build();

It targets Java 17 and later. Responses are immutable record types in io.ipapi.model.

Is the client thread-safe?

Yes. IpApiClient wraps a single java.net.http.HttpClient and is safe to share across threads — build one instance (e.g. a Spring @Bean) and reuse it instead of constructing one per request.

How are nullable fields represented?

Responses are record types accessed with method-style accessors. Nullable fields are reference types that may be null — null-check before use, e.g. info.location().country(). Boolean flags such as info.suspiciousFactors().isVpn() are primitive boolean.

How do I handle rate limits?

Methods throw unchecked IpApiException subtypes. Catch RateLimitException — it exposes getLimit(), getRemaining(), and getReset() from the response headers. The client never retries automatically — schedule your retry after getReset().

Pricing

Pricing

Start using IP-API.io to make your website safer and more user-friendly. Keep out unwanted bots, show visitors content that's relevant to where they are, and spot risky IP addresses quickly. It's perfect for making online shopping more personal and keeping your site secure. Get started today with one of the plans!

Small
€10 /mo
100,000 geo ip requests
10,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Create Account to Subscribe
Medium
€20 /mo
300,000 geo ip requests
25,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Create Account to Subscribe

Need support?

Explore how IP-API.io can enhance your security, provide robust bot protection, and improve IP geolocation accuracy for your applications.

Contact Support

Found a bug in the SDK?

The io.ip-api:ipapi library is open source. Open an issue or pull request on GitHub and we'll take a look.

Open on GitHub