Skip to content

Repository files navigation

ProxyCheck.io V3 API Client for Java

A modern, production-ready Java client for the proxycheck.io v3 API.
Detect proxies, VPNs, TOR nodes, and disposable emails with confidence.

InstallationQuick StartDocumentationContributingLicense

Java 21+proxycheck.io v3GPL-3.0TestsDependencies


Why This Library?

The proxycheck.io API is powerful but raw HTTP calls leave a lot of boilerplate on your plate: retries, caching, rate limiting, response parsing, error handling. This library wraps all of that into a clean, fluent API that a Java developer can pick up in minutes.

  • Zero boilerplate — one line to create a client, one line to check an address.
  • Production-hardened — exponential backoff retries, LRU cache, token-bucket rate limiter.
  • Type-safe — sealed Result types, record-based models, exhaustive switch support.
  • Lightweight — single runtime dependency (Gson). No frameworks, no reflection magic.

Features

FeatureDescription
Sync & Async APIBlocking calls and CompletableFuture-based non-blocking calls
Response CachingLRU cache with configurable TTL and max size
Rate LimitingToken-bucket limiter to stay within API quotas
Exponential BackoffAutomatic retry for transient network and server failures
Address WhitelistSkip API calls entirely for trusted IPs
Event ListenersHook into request, response, error, cache hit, and retry events
Sealed Result TypesExhaustive pattern matching with IpResult and EmailResult
Query Flag Presetsdetailed(), minimal(), withNode() for common configs
Address ValidationClient-side IPv4, IPv6, and email format validation
Result FilteringBuilt-in methods for threat IPs, safe IPs, disposable emails
Batch ProcessingCheck up to 1,000 addresses per request with automatic splitting
JPMS SupportProper module-info.java for modular Java applications

Requirements

  • Java 21 or later
  • Gradle 9+ (included via wrapper) or Maven 3.8+

Installation

Gradle (Kotlin DSL)

repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.SquareCodeFX:proxycheck-io-v3-api:21233954d3")
}

Gradle (Groovy DSL)

dependencies {
implementation 'com.github.SquareCodeFX:proxycheck-io-v3-api:21233954d3'
}

Maven

<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.SquareCodeFX</groupId>
<artifactId>proxycheck-io-v3-api</artifactId>
<version>21233954d3</version>
</dependency>

Building from Source

git clone https://github.com/SquareCodeFX/proxycheck-io-v3-api.git
cd proxycheck-io-v3-api
./gradlew build

Quick Start

importio.proxycheck.api.*;
importio.proxycheck.api.model.*;
// Create a client (uses sensible defaults: 30s timeout, 3 retries)try (varclient = ProxyCheckClient.of("your-api-key")) {
// Check a single IPvarresponse = client.check("8.8.8.8");
response.firstIpResult().ifPresent(ip -> {
System.out.println("Threat: " + ip.isThreat());
System.out.println("Risk: " + ip.riskLevel());
System.out.println("Country: " + ip.countryCode());
});
// Check an emailvaremailResponse = client.check("user@tempmail.org");
emailResponse.firstEmailResult().ifPresent(email ->
System.out.println("Disposable: " + email.isDisposable()));
}

Documentation

Table of Contents


Client Configuration

Use ProxyCheckClient.of(key) for quick setup, or the builder for full control:

varclient = ProxyCheckClient.builder()
.apiKey("your-api-key")
.timeout(Duration.ofSeconds(15))
.cache(Duration.ofMinutes(5), 2000)
.rateLimitPerSecond(150)
.retryPolicy(RetryPolicy.builder()
.maxRetries(3)
.initialDelay(Duration.ofMillis(500))
.multiplier(2.0)
.maxDelay(Duration.ofSeconds(8))
.build())
.whitelist("127.0.0.1", "::1")
.listener(CheckListener.builder()
.onRequest(addrs -> log.info("Checking: {}", addrs))
.onResponse((addrs, resp) -> log.info("Status: {}", resp.status()))
.onError((addrs, err) -> log.error("Failed: {}", err.getMessage()))
.onCacheHit(addr -> log.debug("Cache hit: {}", addr))
.onRetry((attempt, cause) -> log.warn("Retry #{}", attempt))
.build())
.build();

Builder Reference

OptionDefaultDescription
apiKey(String)requiredYour proxycheck.io API key
timeout(Duration)30sHTTP request timeout
httpClient(HttpClient)built-in HTTP/2Custom java.net.http.HttpClient instance
cache(Duration)disabledEnable LRU cache with TTL (default max: 1,000 entries)
cache(Duration, int)disabledEnable LRU cache with TTL and custom max size
rateLimitPerSecond(int)disabledToken-bucket rate limit (recommended: 150)
retryPolicy(RetryPolicy)3 retries, exponential backoffCustom retry behavior
noRetry()Disable all retry attempts
whitelist(String...)emptyAddresses that bypass the API entirely
whitelist(Collection)emptyAddresses that bypass the API entirely
listener(CheckListener)noneLifecycle event listener

Checking Addresses

Single Address

// Default flagsvarresponse = client.check("8.8.8.8");
// With query flagsvarresponse = client.check("8.8.8.8", QueryFlags.detailed());

Batch Check

Batch requests use HTTP POST and automatically split into groups of 1,000 when needed:

// From a collectionvarresponse = client.check(List.of(
"8.8.8.8", "1.1.1.1", "user@example.com"
));
// Varargs shorthandvarresponse = client.checkMultiple("8.8.8.8", "1.1.1.1");
// With flagsvarresponse = client.check(
List.of("8.8.8.8", "1.1.1.1"),
QueryFlags.create().days(7).node(true)
);

Async Check

All check methods have async variants returning CompletableFuture:

// Singleclient.checkAsync("8.8.8.8")
.thenAccept(response -> {
if (response.hasThreat()) {
System.out.println("Threat detected!");
}
});
// Batchclient.checkAsync(List.of("8.8.8.8", "1.1.1.1"))
.thenAccept(response -> response.threatIps().forEach(ip ->
System.out.println("Blocked: " + ip.address())));
// With flagsclient.checkAsync("8.8.8.8", QueryFlags.detailed())
.thenAccept(this::processResponse);

Query Flags

Control the level of detail the API returns:

// Built-in presetsQueryFlags.detailed() // node info + 7-day historyQueryFlags.minimal() // short (flat) response formatQueryFlags.withNode() // include cluster node identifier// Custom combinationvarflags = QueryFlags.create()
.node(true)
.days(30)
.tag("login-flow")
.ver("11-February-2026");
MethodAPI ParameterDescription
node(boolean)nodeInclude responding cluster node
shortResponse(boolean)shortFlat response format
prettyPrint(boolean)pPretty-print JSON
days(int)daysHistorical detection window
tag(String)tagDashboard tracking label
noTag()tag=0Disable query logging
ver(String)verPin API version

Working with Responses

Status Checks

varresponse = client.check("8.8.8.8");
response.isOk(); // status == OKresponse.isWarning(); // status == WARNING (approaching limits)response.isDenied(); // status == DENIED (quota exceeded)response.isError(); // status == ERROR (invalid input)response.isSuccessful(); // OK or WARNING (results are usable)// Resolve to a known status message enumStatusMessagemsg = response.statusMessage();
if (msg == StatusMessage.NEAR_QUERY_LIMIT) {
log.warn("Approaching daily query limit");
}

Accessing Results

// By addressresponse.ipResult("8.8.8.8").ifPresent(ip -> ...);
response.emailResult("user@example.com").ifPresent(email -> ...);
// First result (convenient for single-address checks)response.firstIpResult().ifPresent(ip -> ...);
response.firstEmailResult().ifPresent(email -> ...);
// Unified lookup via the sealed Result interfaceresponse.result("8.8.8.8").ifPresent(result -> {
switch (result) {
caseIpResultip -> handleIp(ip);
caseEmailResultemail -> handleEmail(email);
}
});

Filtering Results

// Built-in filtersList<IpResult> threats = response.threatIps();
List<IpResult> safe = response.safeIps();
List<EmailResult> trash = response.disposableEmails();
List<EmailResult> legit = response.legitimateEmails();
// Boolean checksbooleanhasThreat = response.hasThreat();
booleanhasDisposable = response.hasDisposableEmail();
// Custom predicateList<IpResult> highRisk = response.ipResultsMatching(
ip -> ip.riskLevel() == RiskLevel.VERY_HIGH);
List<IpResult> fromUS = response.ipResultsMatching(
ip -> "US".equals(ip.countryCode()));

Streaming

// Stream all results with pattern matchingresponse.streamResults().forEach(result -> {
switch (result) {
caseIpResultip -> processIp(ip);
caseEmailResultemail -> processEmail(email);
}
});
// Stream only IPsresponse.streamIpResults()
.filter(IpResult::isThreat)
.forEach(ip -> blockAddress(ip.address()));
// Stream only emailsresponse.streamEmailResults()
.filter(EmailResult::isDisposable)
.map(EmailResult::address)
.forEach(this::rejectRegistration);

IP Result Details

Each IpResult aggregates all data sections the API may return:

varip = response.firstIpResult().orElseThrow();
// ── Convenience Accessors ──────────────────────────────────────ip.address(); // "8.8.8.8"ip.isThreat(); // true if proxy, VPN, TOR, compromised, or scraperip.riskLevel(); // LOW | MEDIUM | HIGH | VERY_HIGHip.accessRecommendation(); // ALLOW | CHALLENGE | DENYip.countryCode(); // "US"ip.provider(); // "Google LLC"ip.networkType(); // RESIDENTIAL | BUSINESS | WIRELESS | HOSTING// ── Network ────────────────────────────────────────────────────ip.network().asn(); // "AS15169"ip.network().range(); // "8.8.8.0/24"ip.network().hostname(); // "dns.google"ip.network().provider(); // "Google LLC"ip.network().organisation(); // "Google LLC"ip.network().type(); // "Business"// ── Location ───────────────────────────────────────────────────ip.location().continentName(); // "North America"ip.location().countryName(); // "United States"ip.location().countryCode(); // "US"ip.location().regionName(); // "California"ip.location().cityName(); // "Mountain View"ip.location().latitude(); // "37.386"ip.location().longitude(); // "-122.084"ip.location().timezone(); // "America/Los_Angeles"ip.location().currency(); // Currency[code=USD, name=Dollar, symbol=$]// ── Detections ─────────────────────────────────────────────────ip.detections().isProxy(); // falseip.detections().isVpn(); // falseip.detections().isTor(); // falseip.detections().isCompromised(); // falseip.detections().isScraper(); // falseip.detections().isHosting(); // trueip.detections().isAnonymous(); // falseip.detections().isThreat(); // aggregated: proxy|vpn|tor|compromised|scraperip.detections().risk(); // 0-100ip.detections().confidence(); // 0-100ip.detections().firstSeen(); // ISO 8601 timestamp or nullip.detections().lastSeen(); // ISO 8601 timestamp or null// ── Device Estimate ────────────────────────────────────────────ip.deviceEstimate().address(); // devices behind this IPip.deviceEstimate().subnet(); // devices in the subnet// ── Detection History ──────────────────────────────────────────ip.detectionHistory().delisted(); // true = already delistedip.detectionHistory().delistDatetime(); // ISO 8601 timestamp// ── Attack History ─────────────────────────────────────────────ip.attackHistory().attacks(); // Map<String, Integer>ip.attackHistory().totalAttacks(); // sum of all attack counts// ── Operator (VPN/proxy provider info) ─────────────────────────ip.operator().name(); // "Cloudflare WARP"ip.operator().url(); // "https://..."ip.operator().anonymity(); // "Low"ip.operator().popularity(); // "Very High"ip.operator().services(); // ["VPN"]ip.operator().protocols(); // ["WireGuard"]ip.operator().policies(); // OperatorPolicies record

Email Result Details

varemail = response.firstEmailResult().orElseThrow();
email.address(); // "user@tempmail.org"email.isDisposable(); // true

Risk Assessment

Risk Levels

LevelScoreInterpretation
LOW0 – 25Minimal risk, typically safe
MEDIUM26 – 50Moderate risk, warrants monitoring
HIGH51 – 75Elevated risk, likely suspicious
VERY_HIGH76 – 100Severe risk, strongly associated with abuse
RiskLevellevel = RiskLevel.fromScore(66); // HIGH

Access Recommendations

Combines the risk score with the anonymity flag to produce an access decision:

Risk LevelAnonymousNot Anonymous
LOWCHALLENGEALLOW
MEDIUMCHALLENGECHALLENGE
HIGHDENYCHALLENGE
VERY_HIGHDENYDENY
AccessRecommendationrec = ip.accessRecommendation();
// Or evaluate manuallyAccessRecommendation.evaluate(66, true); // DENYAccessRecommendation.evaluate(10, false); // ALLOW

Caching

The built-in LRU cache stores successful responses keyed by address + query flags:

varclient = ProxyCheckClient.builder()
.apiKey("key")
.cache(Duration.ofMinutes(10), 5000) // TTL, max entries
.build();
client.check("8.8.8.8"); // API callclient.check("8.8.8.8"); // cache hit, no API callclient.invalidateCache("8.8.8.8"); // evict one entryclient.clearCache(); // evict all entries

How it works:

  • Only successful responses (OK or WARNING) are cached
  • Different query flags produce separate cache entries
  • Expired entries are lazily evicted on access
  • When full, the least-recently-used entry is evicted
  • Batch results are cached individually per address for single-lookup hits

Rate Limiting

Protect against API throttling (proxycheck.io warns at 175 req/s, denies at 200 req/s):

varclient = ProxyCheckClient.builder()
.apiKey("key")
.rateLimitPerSecond(150) // stay safely below limits
.build();
Call TypeBehavior When Exhausted
SynchronousBlocks until the next window refill
AsynchronousFails immediately with ProxyCheckException

Retry Policy

Automatic retry with exponential backoff for transient failures:

// Default: 3 retries at 500ms -> 1s -> 2s (capped at 8s)varclient = ProxyCheckClient.of("key");
// Customvarclient = ProxyCheckClient.builder()
.apiKey("key")
.retryPolicy(RetryPolicy.builder()
.maxRetries(5)
.initialDelay(Duration.ofSeconds(1))
.multiplier(3.0)
.maxDelay(Duration.ofSeconds(30))
.build())
.build();
// Disablevarclient = ProxyCheckClient.builder()
.apiKey("key")
.noRetry()
.build();

Retried:IOException (network failures), HTTP 5xx (server errors) Not retried: HTTP 4xx (client errors), parse failures, API rate limit denials


Whitelist

Skip the API entirely for trusted addresses:

varclient = ProxyCheckClient.builder()
.apiKey("key")
.whitelist("127.0.0.1", "::1")
.whitelist(List.of("10.0.0.1", "10.0.0.2"))
.build();
// Returns ProxyCheckResponse.empty() instantlyclient.check("127.0.0.1");

Whitelisted addresses are also filtered out of batch requests before the API call is made.


Event Listeners

Monitor every stage of the request lifecycle:

// Lambda-based buildervarlistener = CheckListener.builder()
.onRequest(addrs -> log.info("Checking {} addresses", addrs.size()))
.onResponse((addrs, resp) -> metrics.record("query_time", resp.queryTime()))
.onError((addrs, err) -> alerting.fire("proxycheck_error", err))
.onCacheHit(addr -> metrics.increment("cache.hits"))
.onRetry((attempt, cause) -> log.warn("Retry #{}: {}", attempt, cause.getMessage()))
.build();
// Register via builder or at runtimeclient.addListener(listener);
client.removeListener(listener);

Or implement the interface directly (all methods are default no-ops):

publicclassMetricsListenerimplementsCheckListener {
@OverridepublicvoidonRequest(Collection<String> addresses) {
metrics.increment("api.requests");
}
@OverridepublicvoidonResponse(Collection<String> addresses, ProxyCheckResponseresponse) {
metrics.record("api.query_time", response.queryTime());
}
}

Address Validation

Client-side format validation for fast feedback before hitting the API:

Addresses.isValidIpv4("192.168.1.1"); // trueAddresses.isValidIpv6("::1"); // trueAddresses.isValidIp("8.8.8.8"); // true (IPv4 or IPv6)Addresses.isValidEmail("user@test.com"); // trueAddresses.isValid("8.8.8.8"); // true (any format)// Throws IllegalArgumentException if invalidAddresses.requireValid("not-an-address");

Error Handling

All errors surface as the unchecked ProxyCheckException:

try {
varresponse = client.check("8.8.8.8");
} catch (ProxyCheckExceptione) {
System.err.println("Message: " + e.getMessage());
if (e.hasHttpStatusCode()) {
System.err.println("HTTP status: " + e.httpStatusCode());
}
// Original cause (IOException, InterruptedException, etc.)System.err.println("Cause: " + e.getCause());
}

Java Module System

This library ships with a module-info.java for JPMS-based applications:

moduleyour.app {
requiresio.proxycheck.api;
}

Exported packages:

PackageContents
io.proxycheck.apiProxyCheckClient, QueryFlags, RetryPolicy, CheckListener, Addresses
io.proxycheck.api.modelProxyCheckResponse, IpResult, EmailResult, Result, enums
io.proxycheck.api.exceptionProxyCheckException

Complete Example

importio.proxycheck.api.*;
importio.proxycheck.api.model.*;
importjava.time.Duration;
importjava.util.List;
publicclassExample {
publicstaticvoidmain(String[] args) {
try (varclient = ProxyCheckClient.builder()
.apiKey("your-api-key")
.cache(Duration.ofMinutes(5))
.rateLimitPerSecond(150)
.whitelist("127.0.0.1", "::1")
.listener(CheckListener.builder()
.onError((addrs, err) ->
System.err.println("Error: " + err.getMessage()))
.build())
.build()) {
// Check mixed IPs and emails in one batchvarresponse = client.check(
List.of("8.8.8.8", "1.1.1.1", "user@tempmail.org"),
QueryFlags.detailed()
);
if (!response.isSuccessful()) {
System.err.println("API error: " + response.message());
return;
}
// Block threatsresponse.threatIps().forEach(ip ->
System.out.printf("BLOCKED %s risk=%d country=%s provider=%s%n",
ip.address(), ip.detections().risk(),
ip.countryCode(), ip.provider()));
// Reject disposable emailsresponse.disposableEmails().forEach(email ->
System.out.printf("REJECTED %s (disposable)%n", email.address()));
// Pattern matching on all resultsresponse.streamResults().forEach(result -> {
switch (result) {
caseIpResultip -> System.out.printf(
"IP %-15s risk=%-9s action=%s%n",
ip.address(), ip.riskLevel(), ip.accessRecommendation());
caseEmailResultemail -> System.out.printf(
"Email %-30s disposable=%s%n",
email.address(), email.isDisposable());
}
});
}
}
}

Architecture

io.proxycheck.api
├── ProxyCheckClient Main client: sync/async checks, caching, retries
├── ProxyCheckClient.Builder Fluent builder for client configuration
├── QueryFlags Query parameter builder (days, node, short, tag, etc.)
├── RetryPolicy Exponential backoff configuration
├── CheckListener Observer interface for lifecycle events
├── ResponseCache TTL + LRU eviction cache (package-private)
├── RateLimiter Token-bucket rate limiter (package-private)
├── ResponseParser JSON-to-model deserialization (package-private)
├── Addresses IPv4/IPv6/email validation utilities
└── Example Comprehensive usage examples
io.proxycheck.api.model
├── ProxyCheckResponse Top-level response with filtering and streaming
├── Result (sealed) Base type for pattern matching
│ ├── IpResult Full IP check result (record)
│ └── EmailResult Email check result (record)
├── Network ASN, range, provider, type
├── Location Geo data: continent → city + coordinates
├── Currency ISO 4217 currency from location
├── DeviceEstimate Device count behind IP/subnet
├── Detections Threat flags + risk/confidence scores
├── DetectionHistory Listing/delisting status
├── AttackHistory Attack type → count map
├── Operator VPN/proxy provider metadata
├── OperatorPolicies Provider policy flags
├── NetworkType (enum) RESIDENTIAL | BUSINESS | WIRELESS | HOSTING
├── RiskLevel (enum) LOW | MEDIUM | HIGH | VERY_HIGH
├── AccessRecommendation ALLOW | CHALLENGE | DENY
├── ResponseStatus (enum) OK | WARNING | DENIED | ERROR
└── StatusMessage (enum) Known API warning/error messages
io.proxycheck.api.exception
└── ProxyCheckException Unchecked exception with optional HTTP status code

Running Tests

./gradlew test

The test suite contains 164 tests covering all client components, model parsing, caching behavior, rate limiting, retry logic, and edge cases.

Dependencies

DependencyVersionPurpose
Gson2.13.2JSON deserialization
JUnit 55.14.1Testing (test scope only)

No transitive dependencies. No frameworks. No annotation processors.

Contributing

Contributions are welcome! Here's how to get started:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (./gradlew test)
  5. Commit with a clear message (git commit -m "Add support for ...")
  6. Push to your branch (git push origin feature/my-feature)
  7. Open a Pull Request

Guidelines

  • Follow existing code style and naming conventions
  • Maintain backward compatibility for public APIs
  • Add Javadoc for all new public methods and classes
  • Keep the single-dependency philosophy — avoid adding new runtime dependencies
  • Target Java 21+ features (records, sealed types, pattern matching)

License

This project is licensed under the GNU General Public License v3.0 — see the LICENSE file for details.


Built with Java 21 • Powered by proxycheck.io

About

Production-ready Java 21+ client for the proxycheck.io v3 API — detect proxies, VPNs, TOR, and disposable emails with built-in caching, rate limiting, and retry.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages