Java · Email breach checks
Check email breaches in Java with HttpClient
This Java example uses Jackson's tree model, which is a pragmatic way to read the email breach response without defining a class for every field. path() returns a missing node instead of null, so reading Title, BreachDate or DataClasses never throws a NullPointerException. When you are ready to map into domain objects, the same JSON binds cleanly to records with @JsonProperty annotations for the PascalCase keys.
The retry loop uses a Java 17 switch with arrow labels to separate outcomes. A 400 becomes an IllegalArgumentException because the input is at fault, a 401 is an IllegalStateException pointing at configuration, and a rate_limited 429 sleeps for Retry-After seconds. A quota_exceeded 429 stops immediately, since waiting will not create credits. Compile it with Jackson on the classpath and pass an address as the first argument.
Steps
- 1
Add jackson-databind to your Gradle or Maven build.
- 2
Export FINOWL_API_KEY in the shell or your application's runtime environment.
- 3
Compile and run EmailBreachCheck with an email address as the first argument.
- 4
Move checkEmail into a Spring service or scheduled job once the output looks right.
# java.net.http ships with the JDK; add Jackson for JSON
# Gradle
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")
# Maven: groupId com.fasterxml.jackson.core, artifactId jackson-databind, version 2.17.2import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class EmailBreachCheck {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final HttpClient HTTP = HttpClient.newHttpClient();
static JsonNode checkEmail(String email) throws Exception {
String body = MAPPER.writeValueAsString(Map.of("email", email));
for (int attempt = 0; attempt < 3; attempt++) {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.finowl.co/v1/breaches/email"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + System.getenv("FINOWL_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode json = MAPPER.readTree(response.body());
if (response.statusCode() == 200) {
return json;
}
String code = json.path("error").path("code").asText("unknown_error");
switch (response.statusCode()) {
case 400 -> throw new IllegalArgumentException("Invalid email address: " + code);
case 401 -> throw new IllegalStateException("Unauthorized: check FINOWL_API_KEY");
case 429 -> {
if (!code.equals("rate_limited")) throw new IllegalStateException("Out of credits: " + code);
long wait = response.headers().firstValueAsLong("Retry-After").orElse(1);
Thread.sleep(Math.max(wait, 1) * 1000);
}
default -> throw new IllegalStateException("Finowl error " + response.statusCode() + ": " + code);
}
}
throw new IllegalStateException("Still rate limited after 3 attempts");
}
public static void main(String[] args) throws Exception {
JsonNode result = checkEmail(args.length > 0 ? args[0] : "alex@example.com");
if (!result.path("breached").asBoolean()) {
System.out.println(result.path("email").asText() + " does not appear in any known breach");
}
for (JsonNode breach : result.path("breaches")) {
List<String> classes = new ArrayList<>();
breach.path("DataClasses").forEach(c -> classes.add(c.asText()));
String flag = breach.path("IsVerified").asBoolean() ? "" : " [unverified]";
System.out.println(breach.path("Title").asText() + " (" + breach.path("BreachDate").asText() + ")" + flag);
System.out.println(" exposed: " + String.join(", ", classes));
}
}
}IN PRODUCTION
Production tips
Create HttpClient and ObjectMapper once and reuse them. Both are thread-safe, and building a new client per request discards pooled connections and adds TLS handshakes.
In Spring Boot, run rechecks from a @Scheduled method with a fixed delay between users rather than a parallel stream, so your account stays within 60 requests per minute.
If you bind to records, add @JsonIgnoreProperties(ignoreUnknown = true) so your code keeps working when you only model a subset of breach fields.
Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.
A FEW GOOD QUESTIONS
Common questions
Related guides
- Check breached passwords in Java at registration
- Predict gender from names in Java 17
- Check email breaches in Python with requests
- Check email breaches in Node.js with fetch
- Check email breaches in TypeScript with typed results
- Check email breaches in Go with net/http
- Email breach checks for fintech signups and logins
- Email breach checks for online stores and shopper logins
- Email breach checks for SaaS logins and admins
Check email breaches in Java with HttpClient
Grab an API key, paste the example and run it. 100 free credits every month.