Skip to content
Empieza con 100 créditos de verificación gratis
Qualisend
Todos los artículos
Ingeniería / 14 de junio de 2026

Cómo validar una dirección de correo electrónico en Java

7 minutes read

Qualisend team
Una ventana de código Java validando un correo a través de las capas de sintaxis, DNS y SMTP hasta llegar a un veredicto de entregabilidad

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.

Your reputation, protected.

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

Get started