Detect whether an IP address belongs to a major cloud provider — offline, with no API calls at lookup time.
This library packs the IP range lists published by each cloud provider into a compact binary lookup table, bundled with the library, so your application can answer "is this a cloud server IP?" with a local binary search.
Both IPv4 and IPv6 are supported.
- Offline lookup — range data ships inside the jar; no network access at runtime
- IPv4 + IPv6 — full CIDR support at any prefix length
- Fast — binary search over ~50,000 blocks: sub-millisecond lookups, ~2 MB of heap
- Provider / region filtering — restrict matching to one provider or one region
- Detailed match results —
findMatchtells you which provider, region, and CIDR block matched - Safe input handling — never throws for bad input, never performs a DNS lookup
- Refreshable data — one Gradle task re-fetches every provider's published ranges
| Provider | Source | IPv4 | IPv6 |
|---|---|---|---|
| Amazon (AWS) | ip-ranges.amazonaws.com | ✅ | ✅ |
| Microsoft (Azure) | ServiceTags (AzureCloud) | ✅ | ✅ |
| Google (GCP) | cloud.json | ✅ | ✅ |
| CloudFlare | ips-v4 / ips-v6 | ✅ | ✅ |
| DigitalOcean | geo feed | ✅ | ✅ |
| Oracle (OCI) | public_ip_ranges.json | ✅ | — |
| Tencent | Chat service IP list API | ✅ | — |
Oracle and Tencent publish IPv4-only feeds.
- JDK 11+
- Kotlin 1.9+ (for Kotlin consumers; works from Java as well)
Published via JitPack. Add the repository and dependency:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
maven("https://jitpack.io")
}
}// build.gradle.kts
dependencies {
implementation("com.github.developerlee79:server-ip-ranges:v1.2.0")
}Groovy DSL
// settings.gradle
dependencyResolutionManagement {
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
// build.gradle
dependencies {
implementation 'com.github.developerlee79:server-ip-ranges:v1.2.0'
}import com.devlee.ipranges.util.IPRangeUtil
class Test {
fun validateIP(ip: String?): Boolean {
return IPRangeUtil.isServerIP(ip)
}
}import com.devlee.ipranges.util.IPRangeUtil
import com.devlee.ipranges.core.provider.Provider
class Test {
fun validateIP(ip: String?): Boolean {
return IPRangeUtil.isServerIP(ip, Provider.Amazon)
}
fun validateIPWithRegion(ip: String?, region: String): Boolean {
return IPRangeUtil.isServerIP(ip, Provider.Amazon, region)
}
}import com.devlee.ipranges.util.IPRangeUtil
class Test {
fun describeIP(ip: String?) {
val match = IPRangeUtil.findMatch(ip) ?: return
println("provider=${match.provider}, region=${match.region}, range=${match.matchedRange}")
}
}null, blank, and non-IP-literal input (including hostnames) returnfalse/null— no exception is thrown for bad input.- Hostnames are rejected before any
InetAddresscall, so no DNS lookup is ever performed. - Addresses are normalized before matching: compressed IPv6 (
2001:db8::1), uppercase hex, and IPv4-mapped IPv6 (::ffff:1.2.3.4) all work. - IPv4 octets with leading zeros (
192.0.2.01) are rejected, since readers disagree on whether010means decimal 10 or octal 8. Send192.0.2.1. - A
falseresult covers both "not a cloud IP" and "unparseable input"; usefindMatchplus your own validation when you need to distinguish them.
- Each provider's published range document is parsed into region-grouped CIDR blocks and committed as
range/<provider>/ip-range.json— the only range data kept in git. - At build time the Gradle
packRangeDatatask turns each file intoranges.bin, a sorted table of network addresses plus prefix lengths, and that table is what ships in the jar. End addresses are implied by the prefix length, and IPv4 blocks are stored in four bytes. - At runtime the table is loaded once per provider and cached — from
./range/<provider>/ip-range.jsonwhen running inside a repo checkout, otherwise from the packed resource in the jar. isServerIP/findMatchparse the input into an unsigned 32-bit or 128-bit integer and binary-search the table.
Providers publish overlapping and nested blocks, so a match walks back from the binary-search position while an enclosing block is still possible. Where blocks overlap, the most specific one is reported.
Providers change their ranges over time. Regenerate the data from a repo checkout:
./gradlew updateRangeFilesThis fetches every provider's live feed, rewrites range/*/ip-range.json, and reports per-provider failures without aborting the whole run. The packed tables are regenerated by the next build; run ./gradlew packRangeData to refresh them on their own. The default ./gradlew test task is hermetic and never touches the network.
Issues and pull requests welcome — provider additions, data corrections, and feature ideas alike.