Skip to content
Finowl

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. 1

    Add jackson-databind to your Gradle or Maven build.

  2. 2

    Export FINOWL_API_KEY in the shell or your application's runtime environment.

  3. 3

    Compile and run EmailBreachCheck with an email address as the first argument.

  4. 4

    Move checkEmail into a Spring service or scheduled job once the output looks right.

Install
# 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.2
Check email breaches in Java with HttpClient
import 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

Check email breaches in Java with HttpClient

Grab an API key, paste the example and run it. 100 free credits every month.