Skip to content
Start with 100 free verification credits
Qualisend
All articles
Engineering / June 14, 2026

How to validate an email address in Java

6 minutes read

Qualisend team
A Java code window validating an email through syntax, DNS, and SMTP layers to a deliverable verdict

Validating an email address in Java is really three checks wearing one name. Most tutorials reach for a regex, confirm the string is shaped like an address, and call it done — but shape is the least useful of the three things you actually want to know. Real validation is layered: a cheap format check, a DNS lookup for the domain's mail route, and an SMTP mailbox probe. Java's standard library and Jakarta Mail cover the first two cleanly; the third is a network problem worth handing off. This guide builds each layer with working code, and shows exactly where each one stops.

The short answer#

Use jakarta.mail.internet.InternetAddress (or Apache Commons Validator) for syntax, the JDK's built-in JNDI DNS provider for the MX lookup, and a verification API for the SMTP mailbox check — cheapest first, short-circuiting as soon as one is decisive. Don't open SMTP connections from your app to probe mailboxes yourself: outbound port 25 is blocked on most hosts, and the answer depends on sending-IP reputation and greylisting you don't want to reimplement. Each layer rules addresses out more cheaply than the last; only the final one can rule an address in.

Layer 1: syntax with Jakarta Mail#

Jakarta Mail — the library renamed from javax.mail when it moved to the Jakarta EE namespace — ships an address parser you can lean on instead of hand-rolling a regex. Construct an InternetAddress and call validate(): the constructor parses the address, and validate() enforces RFC 822 syntax, throwing AddressException on anything malformed.

import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;

public static boolean isValidSyntax(String email) {
    if (email == null || email.length() > 320) {
        return false;
    }
    try {
        InternetAddress address = new InternetAddress(email);
        address.validate();
        return true;
    } catch (AddressException e) {
        return false;
    }
}

One thing to know: validate() is permissive about the domain. It accepts jane@localhost and jane@example — no dot required — because RFC 822 allows dotless domains. That is fine here. Layer 1 is a format gate, not a deliverability check, and layer 2's DNS lookup is what actually decides whether the domain can receive mail. Keep this check permissive and let DNS do the ruling-out; chasing format perfection with a bigger regex is a losing game anyway.

If you're on an older javax.mail jar, the code is identical bar the package name. And if you already depend on Apache Commons Validator, its EmailValidator is a fine layer-one alternative — it's a touch stricter (it rejects dotless domains by default):

import org.apache.commons.validator.routines.EmailValidator;

boolean valid = EmailValidator.getInstance().isValid(email);

Prefer Jakarta Mail if you're already using it to send mail — one fewer dependency. Either library only ever answers the syntax question.

Layer 2: can the domain receive mail?#

A domain with no MX records can't accept mail for anyone, so a single MX lookup eliminates dead domains, misspelled company names, and invented TLDs. Java does this with no external dependency at all: the JDK bundles a DNS service provider for JNDI, so you resolve the MX attribute through a DirContext.

import java.util.Hashtable;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public static boolean hasMailRoute(String domain) {
    Hashtable<String, String> env = new Hashtable<>();
    env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
    env.put("com.sun.jndi.dns.timeout.initial", "2000"); // don't hang on a slow resolver
    env.put("com.sun.jndi.dns.timeout.retries", "1");

    DirContext ctx = null;
    try {
        ctx = new InitialDirContext(env);
        Attributes attrs = ctx.getAttributes(domain, new String[] { "MX" });
        Attribute mx = attrs.get("MX");
        return mx != null && mx.size() > 0;
    } catch (NamingException e) {
        // NameNotFoundException → domain doesn't exist; no MX attribute → no mail route
        return false;
    } finally {
        if (ctx != null) {
            try { ctx.close(); } catch (NamingException ignored) { }
        }
    }
}

Split the domain off the address at the last @ so quoted local parts don't trip you up:

String domain = email.substring(email.lastIndexOf('@') + 1);

hasMailRoute("gmail.com");               // true
hasMailRoute("company-that-folded.com"); // false

A missing or empty MX attribute means the domain publishes no mail route; a NameNotFoundException (a NamingException subclass) means it doesn't resolve at all. Some domains accept mail on an A record with an implicit MX — if you want to honour that edge case, fall back to querying "A" when the MX set comes back empty. For the overwhelming majority of real addresses, an MX check is the right filter. Because the lookup blocks on a DNS resolver, keep the timeouts above in a signup path and treat a transient failure as "check later," not a hard reject — a DNS hiccup shouldn't turn a real customer away.

Layer 3: does the mailbox actually exist?#

Layers 1 and 2 can only rule an address out. A domain can publish perfect MX records and still have no mailbox at the address you're holding — noreply-9f2x@gmail.com is valid syntax on a domain with a live mail route, and it's still a mailbox that was never created. Confirming that a specific mailbox exists means the SMTP delivery conversation: connect to the mail host, issue RCPT TO, read the reply, and disconnect before sending anything. There's more to it — catch-all domains accept every address and defeat a naive probe — how email verification works walks the full pipeline.

You can script SMTP in Java with a raw Socket, but you shouldn't run it from your application. Most cloud providers block outbound port 25, the answer depends on the reputation of the IP you connect from, and receiving servers greylist and rate-limit unfamiliar senders — so a probe that passes in a local test quietly fails, or gets you blocklisted, in production. This is the layer worth delegating.

Qualisend's POST /verify runs the whole pipeline — syntax, DNS, and the SMTP mailbox probe — from reputation-managed infrastructure built for it, and returns a verdict. Java 11+ ships java.net.http.HttpClient, so you need no HTTP dependency; pair it with Jackson (or Gson) to read the JSON. Keep your key in an environment variable and never hard-code it:

import java.io.IOException;
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.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class QualisendClient {
    // Placeholder endpoint — check /developers for the current base URL and shape.
    private static final String ENDPOINT = "https://api.qualisend.com/v1/verify";

    private final HttpClient http = HttpClient.newHttpClient();
    private final ObjectMapper mapper = new ObjectMapper();

    public JsonNode verify(String email) throws IOException, InterruptedException {
        String apiKey = System.getenv("QUALISEND_API_KEY"); // holds YOUR_API_KEY
        String payload = mapper.writeValueAsString(Map.of("email", email));

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(ENDPOINT))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(10))
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();

        HttpResponse<String> response =
            http.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            throw new IOException("Qualisend responded " + response.statusCode());
        }
        // The verdict lives inside a `result` envelope.
        return mapper.readTree(response.body()).get("result");
    }
}

Read the fields you care about off the result node:

JsonNode result = client.verify("jane@example.com");

String  status     = result.get("status").asText();  // deliverable | risky | undeliverable | unknown
int     score      = result.path("score").asInt();    // 0–100 confidence
String  reason     = result.hasNonNull("reason")
                         ? result.get("reason").asText()  // machine-readable reason, may be null
                         : null;
boolean disposable = result.path("sub_flags").path("disposable").asBoolean();

The exact response shape — every sub_flags key and reason code — is in the API reference. For live signup validation the status is usually enough to act on: reject undeliverable, treat risky and unknown by policy, and flag disposable addresses at the input.

Putting the layers together to validate an email address in Java#

Cheapest first, stop as soon as you have an answer. A small record holds the verdict (records are Java 16+; on 11–15 use a plain class):

public record Verdict(String status, String reason) {}

public Verdict validate(String email) throws IOException, InterruptedException {
    if (!isValidSyntax(email)) {
        return new Verdict("undeliverable", "invalid_email");
    }
    String domain = email.substring(email.lastIndexOf('@') + 1);
    if (!hasMailRoute(domain)) {
        return new Verdict("undeliverable", "invalid_domain");
    }
    JsonNode result = client.verify(email); // client is a QualisendClient
    return new Verdict(
        result.get("status").asText(),
        result.hasNonNull("reason") ? result.get("reason").asText() : null);
}

The two local layers cost nothing and catch most junk instantly; the API runs only on addresses worth the network round-trip. That ordering is the whole trick — the same shape you'll find in the Node.js, Python, and PHP versions of this guide, because the layering, not the language, is what makes validation work.

Frequently asked questions#

Is InternetAddress.validate() enough to validate an email in Java?#

For syntax, it's the right layer-one tool — jakarta.mail.internet.InternetAddress with validate() is better tested than a hand-rolled regex and saves you from reimplementing RFC 822. But it validates shape only: it never resolves DNS or contacts a mail server, and it's permissive enough to accept dotless domains like jane@localhost. Pair it with an MX lookup and an SMTP mailbox check before you trust the address.

How do I check MX records in Java without an external library?#

Use the JDK's built-in JNDI DNS provider. Create an InitialDirContext with java.naming.factory.initial set to com.sun.jndi.dns.DnsContextFactory, then call getAttributes(domain, new String[] {"MX"}) and check the returned MX attribute. It needs no third-party dependency and cleanly tells you whether a domain publishes a mail route. Catch NamingException and treat a missing MX as undeliverable.

Can I verify a mailbox in Java without an API?#

Partly. Jakarta Mail and JNDI confirm the address is well-formed and the domain accepts mail — both free and dependency-light. Confirming the mailbox itself means an SMTP conversation, which you can attempt with a raw Socket but shouldn't run from your app server: port 25 is widely blocked and the result depends on your sending IP's reputation. That final layer is what a verification service handles.

Should I validate emails at signup or when cleaning a list?#

Both, at different depths. Run the syntax and MX layers synchronously at signup — they're fast enough to block the request and give instant feedback — and act on the API's status there too. Reserve deeper batch work for list cleaning; the serverless signup guide shows the real-time pattern, and the list-cleaning guide covers the batch side.


Ready to add the SMTP layer? Run any address through the free email checker to watch a syntactically perfect string come back with a real verdict, or read the API reference for the /verify endpoint, the full result envelope, and copy-paste examples in every language above.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started