Validar una dirección de correo electrónico en Java son en realidad tres comprobaciones con un solo nombre. La mayoría de los tutoriales recurren a una expresión regular, confirman que la cadena tiene la forma de una dirección y dan el tema por zanjado, pero la forma es la menos útil de las tres cosas que de verdad quieres saber. La validación real es por capas: una comprobación de formato barata, una consulta DNS de la ruta de correo del dominio y un sondeo del buzón por SMTP. La biblioteca estándar de Java y Jakarta Mail cubren las dos primeras con soltura; la tercera es un problema de red que conviene delegar. Esta guía construye cada capa con código funcional y muestra exactamente dónde se detiene cada una.
La respuesta rápida#
Usa jakarta.mail.internet.InternetAddress (o Apache Commons Validator) para la
sintaxis, el proveedor DNS de JNDI incorporado en el JDK para la consulta MX y
una API de verificación para la comprobación del buzón por SMTP: primero lo más
barato, cortocircuitando en cuanto una de ellas es decisiva. No abras conexiones
SMTP desde tu aplicación para sondear buzones por tu cuenta: el puerto 25
saliente está bloqueado en la mayoría de los hosts, y la respuesta depende de la
reputación de la IP de envío y del greylisting, que no querrás reimplementar.
Cada capa descarta direcciones de forma más barata que la anterior; solo la
última puede dar por buena una dirección.
Capa 1: sintaxis con Jakarta Mail#
Jakarta Mail —la biblioteca que pasó a llamarse así cuando javax.mail se
trasladó al espacio de nombres de Jakarta EE— incluye un analizador de
direcciones en el que puedes apoyarte en lugar de escribir una expresión regular
a mano. Construye un InternetAddress y llama a validate(): el constructor
analiza la dirección, y validate() impone la sintaxis del RFC 822, lanzando
AddressException ante cualquier cosa mal formada.
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;
}
}
Conviene saber una cosa: validate() es permisivo con el dominio. Acepta
jane@localhost y jane@example —sin exigir punto— porque el RFC 822 permite
dominios sin punto. Aquí eso está bien. La capa 1 es una puerta de formato, no
una comprobación de entregabilidad, y la consulta DNS de la capa 2 es la que de
verdad decide si el dominio puede recibir correo. Mantén esta comprobación
permisiva y deja que sea el DNS quien descarte; perseguir la perfección del
formato con una expresión regular más grande es
una batalla perdida de todos modos.
Si trabajas con un jar antiguo de javax.mail, el código es idéntico salvo por
el nombre del paquete. Y si ya dependes de Apache Commons Validator, su
EmailValidator es una buena alternativa para la primera capa: es un pelín más
estricto (rechaza los dominios sin punto por defecto):
import org.apache.commons.validator.routines.EmailValidator;
boolean valid = EmailValidator.getInstance().isValid(email);
Prefiere Jakarta Mail si ya lo usas para enviar correo: una dependencia menos. Cualquiera de las dos bibliotecas solo responde a la pregunta de la sintaxis.
Capa 2: ¿puede el dominio recibir correo?#
Un dominio sin registros MX no puede aceptar correo de nadie, así que una sola
consulta MX elimina dominios muertos, nombres de empresa mal escritos y TLD
inventados. Java lo hace sin ninguna dependencia externa: el JDK incluye un
proveedor de servicio DNS para JNDI, de modo que resuelves el atributo MX a
través de un 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) { }
}
}
}
Separa el dominio de la dirección por la última @ para que las partes
locales entrecomilladas no te den problemas:
String domain = email.substring(email.lastIndexOf('@') + 1);
hasMailRoute("gmail.com"); // true
hasMailRoute("company-that-folded.com"); // false
Un atributo MX ausente o vacío significa que el dominio no publica ninguna
ruta de correo; una NameNotFoundException (una subclase de NamingException)
significa que no resuelve en absoluto. Algunos dominios aceptan correo en un
registro A con un MX implícito; si quieres contemplar ese caso límite, recurre a
consultar "A" cuando el conjunto MX vuelve vacío. Para la inmensa mayoría de
las direcciones reales, una comprobación MX es el filtro adecuado. Como la
consulta se bloquea a la espera de un resolver DNS, mantén los tiempos de espera
de arriba en una ruta de registro y trata un fallo transitorio como «comprobar
más tarde», no como un rechazo tajante: un tropiezo del DNS no debería ahuyentar
a un cliente real.
Capa 3: ¿existe de verdad el buzón?#
Las capas 1 y 2 solo pueden descartar una dirección. Un dominio puede publicar
registros MX perfectos y aun así no tener ningún buzón en la dirección que
manejas: noreply-9f2x@gmail.com es sintaxis válida en un dominio con una ruta
de correo activa, y sigue siendo un buzón que nunca se creó. Confirmar que
existe un buzón concreto implica la conversación de entrega SMTP: conectar con
el host de correo, emitir RCPT TO, leer la respuesta y desconectar antes de
enviar nada. Hay más matices —los dominios catch-all aceptan cualquier dirección
y desbaratan un sondeo ingenuo— y
cómo funciona la verificación de correo
recorre el proceso completo.
Puedes programar SMTP en Java con un Socket en crudo, pero no deberías
ejecutarlo desde tu aplicación. La mayoría de los proveedores en la nube
bloquean el puerto 25 saliente, la respuesta depende de la reputación de la IP
desde la que te conectas, y los servidores receptores aplican greylisting y
limitan la tasa a remitentes desconocidos, de modo que un sondeo que pasa en una
prueba local falla silenciosamente, o te mete en una lista negra, en producción.
Esta es la capa que merece la pena delegar.
El POST /verify de Qualisend ejecuta todo el proceso —sintaxis,
DNS y el sondeo del buzón por SMTP— desde una infraestructura de reputación
gestionada creada para ello, y devuelve un veredicto. Java 11+ incluye
java.net.http.HttpClient, así que no necesitas ninguna dependencia HTTP;
combínalo con Jackson (o Gson) para leer el JSON. Guarda tu clave en una variable
de entorno y nunca la escribas fija en el 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");
}
}
Lee los campos que te interesan del nodo 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();
La forma exacta de la respuesta —cada clave de sub_flags y cada código de
reason— está en la referencia de la API. Para la validación en
vivo durante el registro, el status suele bastar para actuar: rechaza
undeliverable, gestiona risky y unknown según tu política, y marca las
direcciones disposable en el propio campo de entrada.
Uniendo las capas para validar una dirección de correo en Java#
Primero lo más barato, y detente en cuanto tengas una respuesta. Un pequeño record guarda el veredicto (los records son de Java 16+; en 11–15 usa una clase normal):
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);
}
Las dos capas locales no cuestan nada y atrapan al instante casi toda la basura; la API se ejecuta solo sobre las direcciones que merecen el ida y vuelta de red. Ese orden es todo el truco: la misma estructura que encontrarás en las versiones de esta guía para Node.js, Python y PHP, porque lo que hace que la validación funcione es el enfoque por capas, no el lenguaje.
Preguntas frecuentes#
¿Basta con InternetAddress.validate() para validar un correo en Java?#
Para la sintaxis, es la herramienta adecuada en la primera capa:
jakarta.mail.internet.InternetAddress con validate() está mejor probado que
una expresión regular hecha a mano y te ahorra reimplementar el RFC 822. Pero
solo valida la forma: nunca resuelve DNS ni contacta con un servidor de correo, y
es lo bastante permisivo como para aceptar dominios sin punto como
jane@localhost. Combínalo con una consulta MX y una comprobación del buzón por
SMTP antes de fiarte de la dirección.
¿Cómo compruebo los registros MX en Java sin una biblioteca externa?#
Usa el proveedor DNS de JNDI incorporado en el JDK. Crea un InitialDirContext
con java.naming.factory.initial establecido en com.sun.jndi.dns.DnsContextFactory,
luego llama a getAttributes(domain, new String[] {"MX"}) y comprueba el
atributo MX devuelto. No necesita ninguna dependencia de terceros y te dice con
claridad si un dominio publica una ruta de correo. Captura NamingException y
trata un MX ausente como no entregable.
¿Puedo verificar un buzón en Java sin una API?#
En parte. Jakarta Mail y JNDI confirman que la dirección tiene el formato
correcto y que el dominio acepta correo, ambos gratuitos y con pocas
dependencias. Confirmar el buzón en sí implica una conversación SMTP, que puedes
intentar con un Socket en crudo pero no deberías ejecutar desde tu servidor de
aplicaciones: el puerto 25 está bloqueado con frecuencia y el resultado depende
de la reputación de tu IP de envío. Esa última capa es la que gestiona un
servicio de verificación.
¿Debo validar los correos en el registro o al limpiar una lista?#
Ambas cosas, con distinta profundidad. Ejecuta las capas de sintaxis y MX de
forma síncrona en el registro —son lo bastante rápidas como para bloquear la
petición y ofrecer feedback instantáneo— y actúa también sobre el status de la
API en ese punto. Reserva el trabajo por lotes más profundo para la limpieza de
listas; la
guía de registro serverless muestra
el patrón en tiempo real, y la
guía de limpieza de listas cubre la parte por
lotes.
¿Listo para añadir la capa SMTP? Pasa cualquier dirección por el
comprobador de correo gratuito para ver cómo una cadena
sintácticamente perfecta vuelve con un veredicto real, o lee la
referencia de la API para conocer el endpoint /verify, el sobre
result completo y ejemplos listos para copiar y pegar en todos los lenguajes
anteriores.