Validare un indirizzo email in Java significa in realtà fare tre controlli che vanno sotto un unico nome. La maggior parte dei tutorial ricorre a una regex, conferma che la stringa ha la forma di un indirizzo e considera chiusa la questione, ma la forma è la meno utile delle tre cose che vuoi davvero sapere. La vera validazione è a livelli: un controllo di formato economico, una ricerca DNS dell'instradamento della posta del dominio e un test SMTP della casella. La libreria standard di Java e Jakarta Mail coprono in modo pulito i primi due; il terzo è un problema di rete che conviene delegare. Questa guida costruisce ogni livello con codice funzionante e mostra esattamente dove si ferma ciascuno.
In breve#
Usa jakarta.mail.internet.InternetAddress (o Apache Commons Validator) per la
sintassi, il provider DNS per JNDI integrato nel JDK per la ricerca MX e un'API
di verifica per il controllo SMTP della casella: prima il più economico,
fermandoti appena uno dei controlli è decisivo. Non aprire connessioni SMTP dalla
tua app per testare direttamente le caselle: la porta 25 in uscita è bloccata dalla
maggior parte degli host e la risposta dipende dalla reputazione dell'IP di invio
e dal greylisting, cose che non vuoi reimplementare. Ogni livello esclude
indirizzi a un costo minore dell'ultimo, e solo l'ultimo può confermarne uno.
Livello 1: la sintassi con Jakarta Mail#
Jakarta Mail (la libreria nata come javax.mail e rinominata quando è passata
al namespace Jakarta EE) include un parser di indirizzi su cui puoi contare
invece di scrivere a mano una regex. Crea un InternetAddress e chiama
validate(): il costruttore analizza l'indirizzo e validate() applica la
sintassi della RFC 822, lanciando un'AddressException per qualsiasi input
malformato.
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;
}
}
Una cosa da sapere: validate() è permissivo sul dominio. Accetta
jane@localhost e jane@example, senza bisogno del punto, perché la RFC 822
ammette domini senza punto. In questo caso va bene. Il livello 1 è un filtro sul
formato, non un controllo di deliverability, ed è la ricerca DNS del livello 2 a
decidere davvero se il dominio può ricevere posta. Mantieni permissivo questo
controllo e lascia al DNS il compito di escludere; inseguire la perfezione del
formato con una regex più grande è comunque
una battaglia persa.
Se usi un vecchio jar di javax.mail, il codice è identico a parte il nome del
package. E se dipendi già da Apache Commons Validator, il suo EmailValidator è
una buona alternativa per il primo livello: è un po' più rigido (rifiuta i domini
senza punto per impostazione predefinita):
import org.apache.commons.validator.routines.EmailValidator;
boolean valid = EmailValidator.getInstance().isValid(email);
Preferisci Jakarta Mail se la usi già per inviare posta: una dipendenza in meno. In ogni caso, entrambe le librerie rispondono solo alla domanda sulla sintassi.
Livello 2: il dominio può ricevere posta?#
Un dominio senza record MX non può accettare posta per nessuno, quindi una sola
ricerca MX elimina domini inattivi, nomi aziendali scritti male e TLD inventati.
Java lo fa senza alcuna dipendenza esterna: il JDK include un service provider
DNS per JNDI, quindi risolvi l'attributo MX tramite 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 il dominio dall'indirizzo in corrispondenza dell'ultima @, così le
parti locali tra virgolette non ti creano problemi:
String domain = email.substring(email.lastIndexOf('@') + 1);
hasMailRoute("gmail.com"); // true
hasMailRoute("company-that-folded.com"); // false
Un attributo MX mancante o vuoto significa che il dominio non pubblica alcun
instradamento della posta; una NameNotFoundException (una sottoclasse di
NamingException) significa che il dominio non si risolve affatto. Alcuni
domini accettano posta su un record A con un MX implicito: se vuoi gestire anche
questo caso limite, ripiega su una query "A" quando l'insieme MX torna vuoto.
Per la stragrande maggioranza degli indirizzi reali, un controllo MX è il filtro
giusto. Dato che la ricerca resta in attesa di un resolver DNS, mantieni i
timeout visti sopra in un flusso di registrazione e tratta un errore temporaneo
come "da ricontrollare più tardi", non come un rifiuto netto: un intoppo del DNS
non dovrebbe allontanare un cliente reale.
Livello 3: la casella esiste davvero?#
I livelli 1 e 2 possono solo escludere un indirizzo. Un dominio può pubblicare
record MX perfetti e non avere comunque alcuna casella all'indirizzo che hai in
mano: noreply-9f2x@gmail.com ha una sintassi valida su un dominio con un instradamento di posta attivo, ma resta una casella mai creata.
Confermare che una casella specifica esista richiede la conversazione SMTP di consegna: collegarsi all'host di posta, inviare RCPT TO, leggere la risposta e disconnettersi prima di inviare qualsiasi cosa. C'è dell'altro (i domini
catch-all accettano qualsiasi indirizzo e rendono inutile un test ingenuo):
come funziona la verifica email illustra
l'intera pipeline.
Puoi implementare il dialogo SMTP in Java con un Socket grezzo, ma non dovresti farlo dalla tua
applicazione. La maggior parte dei provider cloud blocca la porta 25 in uscita,
la risposta dipende dalla reputazione dell'IP da cui ti colleghi e i server
riceventi applicano il greylisting e limitano la frequenza dei mittenti che non
conoscono: così un test che funziona in locale, in produzione smette di
funzionare senza avvisi o ti fa finire in blacklist. È questo il livello che
conviene delegare.
POST /verify di Qualisend esegue l'intera pipeline
(sintassi, DNS e test SMTP della casella) da un'infrastruttura con reputazione
gestita, costruita apposta, e restituisce un esito. Java 11+ include
java.net.http.HttpClient, quindi non ti serve alcuna dipendenza HTTP; abbinalo
a Jackson (o Gson) per leggere il JSON. Tieni la chiave in una variabile
d'ambiente e non scriverla mai nel codice:
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");
}
}
Leggi i campi che ti interessano dal 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 esatta della risposta, con ogni chiave di sub_flags e ogni codice di
reason, è nel riferimento API. Per la validazione in tempo
reale in fase di registrazione, di solito lo status basta per decidere:
rifiuta undeliverable, gestisci risky e unknown secondo le tue regole e
segnala gli indirizzi disposable già nel campo di input.
Mettere insieme i livelli per validare un indirizzo email in Java#
Prima il più economico, e fermati appena hai una risposta. Un piccolo record contiene l'esito (i record richiedono Java 16+; dalla 11 alla 15 usa una classe normale):
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);
}
I due livelli locali non costano nulla e intercettano subito la maggior parte degli indirizzi spazzatura; l'API viene chiamata solo per gli indirizzi che meritano la chiamata di rete. Tutto il trucco sta in quell'ordine: è la stessa struttura che trovi nelle versioni di questa guida per Node.js, Python e PHP, perché a far funzionare la validazione è la suddivisione in livelli, non il linguaggio.
Domande frequenti#
InternetAddress.validate() basta per validare un'email in Java?#
Per la sintassi è lo strumento giusto del primo livello:
jakarta.mail.internet.InternetAddress con validate() è testato meglio di una
regex scritta a mano e ti evita di reimplementare la RFC 822. Ma valida solo la
forma: non risolve mai il DNS né contatta un server di posta, ed è abbastanza
permissivo da accettare domini senza punto come jane@localhost. Abbinalo a una
ricerca MX e a un controllo SMTP della casella prima di fidarti dell'indirizzo.
Come controllo i record MX in Java senza una libreria esterna?#
Usa il provider DNS per JNDI integrato nel JDK. Crea un InitialDirContext con
java.naming.factory.initial impostato su com.sun.jndi.dns.DnsContextFactory,
poi chiama getAttributes(domain, new String[] {"MX"}) e controlla l'attributo
MX restituito. Non richiede dipendenze di terze parti e ti dice chiaramente se
un dominio pubblica un instradamento della posta. Intercetta NamingException e
considera non recapitabile un indirizzo con MX mancante.
Posso verificare una casella in Java senza un'API?#
In parte. Jakarta Mail e JNDI confermano che l'indirizzo è ben formato e che il
dominio accetta posta, entrambi gratis e con poche dipendenze. Confermare la
casella vera e propria richiede una conversazione SMTP, che puoi tentare con un
Socket grezzo ma che non dovresti eseguire dal tuo server applicativo: la porta 25 è bloccata quasi ovunque e il risultato dipende dalla reputazione del tuo IP
di invio. È di quest'ultimo livello che si occupa un servizio di verifica.
Meglio validare le email in fase di registrazione o durante la pulizia di una lista?#
Entrambe le cose, con una profondità diversa. Esegui i livelli di sintassi e MX
in modo sincrono in fase di registrazione (sono abbastanza veloci da bloccare la
richiesta e dare un riscontro immediato) e lì agisci anche in base allo status
restituito dall'API. Riserva il lavoro in blocco più approfondito alla pulizia
delle liste; la
guida alla verifica serverless in fase di registrazione
mostra lo schema in tempo reale, e la
guida alla pulizia delle liste copre la parte
in blocco.
Vuoi aggiungere il livello SMTP? Prova qualsiasi indirizzo con la
verifica email gratuita per vedere una stringa
sintatticamente perfetta tornare con un esito reale, oppure leggi il
riferimento API per l'endpoint /verify, l'oggetto result
completo ed esempi pronti da copiare e incollare in tutti i linguaggi citati
sopra.



