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:
| Address | Passes @Email | Problem |
|---|---|---|
jane@gmial.com | yes | The domain is a typo and does not exist |
former.employee@example.com | yes | The mailbox was closed |
jane@example-site.com | yes | The domain has no MX record, so it receives no mail |
x7k2@temp-inbox.example | yes | Disposable 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:
- accept: the mail server accepted the mailbox and no risk signal advises against it.
- reject: the address cannot receive mail. The reason says why (
rejected_email,invalid_domain,no_mx_record…). - suggest:
didYouMeanhas a correction, for exampletypo@gmail.comfortypo@gmial.com. - review: an unknown result, a catch-all domain or a disposable address. Unknown is not invalid: the server timed out or asked to try later.
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:
| Address | Answer |
|---|---|
valid@sandbox.easyemailverification.com | valid, accepted_email |
invalid@sandbox.easyemailverification.com | invalid, rejected_email |
unknown@sandbox.easyemailverification.com | unknown, timeout |
catchall@sandbox.easyemailverification.com | valid, accept_all: true, safe_to_send: false |
disposable@sandbox.easyemailverification.com | valid, disposable: true, safe_to_send: false |
typo@gmial.com | invalid, did_you_mean: typo@gmail.com |
quota@sandbox.easyemailverification.com | HTTP 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
- Email Verification API: plans, limits and the other endpoints.
- Result codes: every
result,reasonand risk signal. - Examples in other languages on GitHub.
More ways to verify emails
- Email validation in Node.js: from regex to mailbox checks
- How to validate an email address in Python
- Validate email addresses in PHP without sending a message
- What every result code means
- API reference
- Check a single address for free
- All integrations
Using an AI assistant? It can follow this guide with you: give it validate-email-java-spring-boot.md.