Email validation in Java with Spring Boot

Create a free accountPlans and credits

In Spring Boot, email validation usually means the @Email annotation from Jakarta Bean Validation. It checks the format, and it accepts more than most developers expect. This guide shows what @Email does and does not catch, then adds a mailbox check with the Easy Email Verification API through Spring's RestClient, ending with a signup controller. The code was tested with Spring Boot 3.5 and Java 17, and every example works with the free sandbox key eev_sandbox_key.

1. Check the format with @Email

Add spring-boot-starter-validation and annotate the request:

public record SignupRequest(@NotBlank @Email String email) {}

With @Valid on the controller parameter, a badly written address such as not-an-email is rejected with HTTP 400 before your code runs.

Know its limits: Hibernate Validator's @Email accepts jane@example, a domain without a dot, because that form is legal in the email standard. If your users must give internet addresses, add a pattern:

@Email(regexp = "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$")

Why format validation is not enough

All of these pass @Email and still bounce:

AddressPasses @EmailProblem
jane@gmial.comyesThe domain is a typo and does not exist
former.employee@example.comyesThe mailbox was closed
jane@example-site.comyesThe domain has no MX record, so it receives no mail
x7k2@temp-inbox.exampleyesDisposable inbox that disappears in a few minutes

Finding them takes a DNS lookup and an SMTP conversation with the mail server. Doing that from your own application is fragile: cloud providers often block outgoing port 25, and mail servers greylist or block unknown hosts that probe mailboxes. An email verification API does it for you, without sending any email.

2. Map the API response

The API returns JSON. A record with the fields you need is enough; unknown fields are ignored:

@JsonIgnoreProperties(ignoreUnknown = true)
public record EmailVerification(
        String email,
        String result,
        String reason,
        boolean disposable,
        @JsonProperty("accept_all") boolean acceptAll,
        boolean role,
        @JsonProperty("safe_to_send") boolean safeToSend,
        @JsonProperty("did_you_mean") String didYouMean) {

    public String decision() {
        if (didYouMean != null && !didYouMean.isEmpty()) return "suggest"; // ask the user: did you mean ...?
        if ("valid".equals(result) && safeToSend) return "accept";
        if ("invalid".equals(result)) return "reject";
        return "review"; // unknown, catch-all or disposable: your policy decides
    }
}

result is valid, invalid or unknown:

3. Call the API with RestClient

The key comes from the eev.api-key property (for example EEV_API_KEY in the environment, mapped in application.properties as eev.api-key=${EEV_API_KEY:eev_sandbox_key}). Keep it on the server.

@Component
public class EmailVerificationClient {

    private final RestClient http;

    public EmailVerificationClient(RestClient.Builder builder,
                                   @Value("${eev.api-key:eev_sandbox_key}") String apiKey) {
        this.http = builder
                .baseUrl("https://api.easyemailverification.com/v1")
                .defaultHeader("X-API-Key", apiKey)
                .build();
    }

    public EmailVerification verify(String email) {
        return http.get()
                .uri(uri -> uri.path("/verify").queryParam("email", "{email}").build(email))
                .retrieve()
                .body(EmailVerification.class);
    }

    public List<EmailVerification> verifyAll(List<String> emails) { // up to 50 per request
        return http.post()
                .uri("/verify")
                .contentType(MediaType.APPLICATION_JSON)
                .body(Map.of("emails", emails))
                .retrieve()
                .body(new ParameterizedTypeReference<List<EmailVerification>>() {});
    }
}

Passing the address as a URI variable ("{email}" plus build(email)) makes Spring encode it fully, so a + in jane+news@example.com reaches the API intact. Errors (401 unknown key, 402 no credits) raise a RestClientResponseException.

4. Use it in a signup controller

The controller rejects undeliverable addresses, offers the typo correction and never blocks a signup only because the verification could not run:

@RestController
public class SignupController {

    public record SignupRequest(@NotBlank @Email String email) {}

    private final EmailVerificationClient verifier;

    public SignupController(EmailVerificationClient verifier) {
        this.verifier = verifier;
    }

    @PostMapping("/signup")
    public ResponseEntity<Map<String, Object>> signup(@Valid @RequestBody SignupRequest request) {
        EmailVerification result;
        try {
            result = verifier.verify(request.email().trim());
        } catch (RestClientException e) {
            result = null; // verification unavailable: do not block the signup
        }
        String decision = result == null ? "review" : result.decision();
        return switch (decision) {
            case "reject" -> ResponseEntity.badRequest().body(Map.of("error", "This email address cannot receive mail."));
            case "suggest" -> ResponseEntity.badRequest().body(Map.of("error", "Did you mean " + result.didYouMean() + "?"));
            // "accept" or "review": create the account; for "review", confirm the address by email
            default -> ResponseEntity.ok(Map.of("ok", true, "needsConfirmation", decision.equals("review")));
        };
    }
}

Dependencies: spring-boot-starter-web and spring-boot-starter-validation. For whole files with thousands of addresses, use the bulk endpoints described in the API reference instead of a loop.

Test addresses

With the key eev_sandbox_key, these addresses return fixed answers and use no credits:

AddressAnswer
valid@sandbox.easyemailverification.comvalid, accepted_email
invalid@sandbox.easyemailverification.cominvalid, rejected_email
unknown@sandbox.easyemailverification.comunknown, timeout
catchall@sandbox.easyemailverification.comvalid, accept_all: true, safe_to_send: false
disposable@sandbox.easyemailverification.comvalid, disposable: true, safe_to_send: false
typo@gmial.cominvalid, did_you_mean: typo@gmail.com
quota@sandbox.easyemailverification.comHTTP 402 (no credits)

They make good integration tests: assert that valid@ gives accept, invalid@ gives reject and typo@gmial.com gives suggest. For production, create a free account, generate a key under API settings and set EEV_API_KEY. Each verified address uses one credit; the free plan includes 50 verifications a day.

Next steps

By using this website, you automatically accept that we use cookies.