Validar um endereço de e-mail em Java são, na verdade, três checagens sob um mesmo nome. A maioria dos tutoriais recorre a uma regex, confirma que a string tem o formato de um endereço e considera pronto — mas o formato é a menos útil das três coisas que você realmente quer saber. A validação de verdade é em camadas: uma checagem barata de formato, uma consulta DNS pela rota de e-mail do domínio e uma sondagem SMTP da caixa postal. A biblioteca padrão do Java e o Jakarta Mail cobrem as duas primeiras de forma limpa; a terceira é um problema de rede que vale a pena delegar. Este guia constrói cada camada com código funcional e mostra exatamente onde cada uma para.
A resposta curta#
Use jakarta.mail.internet.InternetAddress (ou o Apache Commons Validator) para
a sintaxe, o provedor DNS JNDI embutido no JDK para a consulta de MX e uma API de
verificação para a checagem da caixa postal por SMTP — a mais barata primeiro,
interrompendo assim que uma delas for decisiva. Não abra conexões SMTP a partir
da sua aplicação para sondar caixas postais por conta própria: a porta 25 de
saída é bloqueada na maioria dos hosts, e a resposta depende da reputação do IP
de envio e de greylisting que você não vai querer reimplementar. Cada camada
descarta endereços de forma mais barata que a anterior; só a última consegue
aprovar um endereço.
Camada 1: sintaxe com o Jakarta Mail#
O Jakarta Mail — a biblioteca renomeada de javax.mail quando migrou para o
namespace Jakarta EE — traz um parser de endereços em que você pode se apoiar em
vez de escrever uma regex na mão. Construa um InternetAddress e chame
validate(): o construtor faz o parsing do endereço, e validate() impõe a
sintaxe da RFC 822, lançando AddressException em qualquer coisa malformada.
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;
}
}
Um detalhe importante: validate() é permissivo quanto ao domínio. Ele aceita
jane@localhost e jane@example — sem exigir ponto — porque a RFC 822 permite
domínios sem ponto. E tudo bem aqui. A camada 1 é um portão de formato, não uma
checagem de entregabilidade, e é a consulta DNS da camada 2 que de fato decide
se o domínio consegue receber e-mail. Mantenha essa checagem permissiva e deixe
o DNS fazer o descarte; perseguir a perfeição do formato com uma regex maior é
uma batalha perdida de qualquer forma.
Se você estiver em um jar javax.mail mais antigo, o código é idêntico, exceto
pelo nome do pacote. E se você já depende do Apache Commons Validator, o
EmailValidator dele é uma boa alternativa para a camada um — é um pouco mais
rígido (rejeita domínios sem ponto por padrão):
import org.apache.commons.validator.routines.EmailValidator;
boolean valid = EmailValidator.getInstance().isValid(email);
Prefira o Jakarta Mail se você já o usa para enviar e-mail — uma dependência a menos. Qualquer uma das bibliotecas só responde à pergunta da sintaxe.
Camada 2: o domínio consegue receber e-mail?#
Um domínio sem registros MX não consegue aceitar e-mail para ninguém, então uma
única consulta de MX elimina domínios mortos, nomes de empresas com erro de
digitação e TLDs inventados. O Java faz isso sem nenhuma dependência externa: o
JDK inclui um provedor de serviço DNS para o JNDI, então você resolve o atributo
MX através de um 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) { }
}
}
}
Separe o domínio do endereço no último @ para que partes locais entre aspas
não te atrapalhem:
String domain = email.substring(email.lastIndexOf('@') + 1);
hasMailRoute("gmail.com"); // true
hasMailRoute("company-that-folded.com"); // false
Um atributo MX ausente ou vazio significa que o domínio não publica rota de
e-mail; uma NameNotFoundException (subclasse de NamingException) significa
que ele não resolve de forma alguma. Alguns domínios aceitam e-mail em um
registro A com um MX implícito — se você quiser respeitar esse caso extremo,
recorra à consulta de "A" quando o conjunto de MX voltar vazio. Para a esmagadora
maioria dos endereços reais, uma checagem de MX é o filtro certo. Como a consulta
bloqueia esperando um resolver DNS, mantenha os timeouts acima em um fluxo de
cadastro e trate uma falha transitória como "verificar depois", não como uma
rejeição definitiva — um soluço do DNS não deveria afastar um cliente real.
Camada 3: a caixa postal realmente existe?#
As camadas 1 e 2 só conseguem descartar um endereço. Um domínio pode publicar
registros MX perfeitos e ainda assim não ter caixa postal no endereço que você
tem em mãos — noreply-9f2x@gmail.com tem sintaxe válida em um domínio com rota
de e-mail ativa, e ainda assim é uma caixa postal que nunca foi criada. Confirmar
que uma caixa postal específica existe exige a conversa de entrega SMTP:
conectar ao host de e-mail, emitir RCPT TO, ler a resposta e desconectar antes
de enviar qualquer coisa. Há mais nuances — domínios catch-all aceitam qualquer
endereço e derrotam uma sondagem ingênua —
como funciona a verificação de e-mail
percorre o pipeline completo.
Você pode roteirizar SMTP em Java com um Socket cru, mas não deveria rodá-lo a
partir da sua aplicação. A maioria dos provedores de nuvem bloqueia a porta 25 de
saída, a resposta depende da reputação do IP a partir do qual você conecta, e os
servidores de recebimento aplicam greylisting e limitam a taxa de remetentes
desconhecidos — então uma sondagem que passa em um teste local silenciosamente
falha, ou te coloca em uma blocklist, em produção. Esta é a camada que vale a
pena delegar.
O POST /verify da Qualisend roda o pipeline inteiro — sintaxe,
DNS e a sondagem SMTP da caixa postal — a partir de uma infraestrutura com
reputação gerenciada, feita para isso, e devolve um veredito. O Java 11+ já traz
java.net.http.HttpClient, então você não precisa de nenhuma dependência HTTP;
combine-o com o Jackson (ou o Gson) para ler o JSON. Guarde sua chave em uma
variável de ambiente e nunca a deixe embutida no código:
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");
}
}
Leia os campos que te interessam a partir do nó result:
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();
O formato exato da resposta — cada chave de sub_flags e cada código de reason
— está na referência da API. Para a validação de cadastro ao vivo,
o status geralmente basta para agir: rejeite undeliverable, trate risky e
unknown conforme sua política e sinalize endereços disposable já na entrada.
Juntando as camadas para validar um endereço de e-mail em Java#
A mais barata primeiro, pare assim que tiver uma resposta. Um pequeno record guarda o veredito (records são do Java 16+; no 11–15, use uma classe comum):
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);
}
As duas camadas locais não custam nada e pegam a maior parte do lixo na hora; a API roda apenas nos endereços que valem a ida e volta pela rede. Essa ordem é o truque inteiro — o mesmo formato que você encontra nas versões em Node.js, Python e PHP deste guia, porque é o encadeamento das camadas, não a linguagem, que faz a validação funcionar.
Perguntas frequentes#
InternetAddress.validate() basta para validar um e-mail em Java?#
Para a sintaxe, é a ferramenta certa da camada um — o
jakarta.mail.internet.InternetAddress com validate() é mais bem testado que
uma regex caseira e livra você de reimplementar a RFC 822. Mas ele valida apenas
o formato: nunca resolve DNS nem contata um servidor de e-mail, e é permissivo o
bastante para aceitar domínios sem ponto, como jane@localhost. Combine-o com
uma consulta de MX e uma verificação de caixa postal por SMTP antes de confiar no
endereço.
Como consultar registros MX em Java sem uma biblioteca externa?#
Use o provedor DNS JNDI embutido no JDK. Crie um InitialDirContext com
java.naming.factory.initial definido como com.sun.jndi.dns.DnsContextFactory,
depois chame getAttributes(domain, new String[] {"MX"}) e verifique o atributo
MX retornado. Não precisa de nenhuma dependência de terceiros e informa de
forma limpa se um domínio publica uma rota de e-mail. Capture NamingException e
trate a ausência de MX como não entregável.
Dá para verificar uma caixa postal em Java sem uma API?#
Em parte. O Jakarta Mail e o JNDI confirmam que o endereço está bem formado e que
o domínio aceita e-mail — ambos gratuitos e com poucas dependências. Confirmar a
própria caixa postal exige uma conversa SMTP, que você pode tentar com um
Socket cru, mas não deveria rodar do seu servidor de aplicação: a porta 25 é
amplamente bloqueada e o resultado depende da reputação do seu IP de envio. Essa
camada final é o que um serviço de verificação cuida.
Devo validar e-mails no cadastro ou ao limpar uma lista?#
Nos dois, com profundidades diferentes. Rode as camadas de sintaxe e MX de forma
síncrona no cadastro — elas são rápidas o suficiente para bloquear a requisição e
dar feedback instantâneo — e aja sobre o status da API ali também. Reserve o
trabalho em lote mais profundo para a limpeza de listas; o
guia de cadastro serverless mostra o
padrão em tempo real, e o
guia de limpeza de listas cobre o lado em
lote.
Pronto para adicionar a camada SMTP? Passe qualquer endereço pelo
verificador de e-mail gratuito para ver uma string com
sintaxe perfeita voltar com um veredito real, ou leia a
referência da API para o endpoint /verify, o envelope result
completo e exemplos de copiar e colar em todas as linguagens acima.