Java Extended Email Validation Using DNS MX Lookup

 (0)  (0)

I have on several occasions been asked what is the best way to validate an email address.  There is no simple answer, once upon a time you could query mail-exchanges (MX) directly to validate a user account but unless you have an internal mail server that is secured from the web then this is not likely to be possible.   Over the years more roust email validations have been requested to avoid the obvious mistakes made by user error or purposeful entered false details.  

The solution to the misspelling of the email address used to be resolved with the entry of the email address twice (they say you are unlikely to make the same mistake twice) but this has become seen as a hindrance when viewed from the user experience (UX) perspective.  So as developers we have to come up with more elaborate means of testing for user mistakes.   There is also the case where users deliberately provide false information to access your service, this has also been handed to the developer to resolve this without interrupting the UX.  It is to this end I have developed the following code snippet.

This is not the be-all and end-all solution to email validation and I am more than open to suggestions on improving this.  My original design was implemented in PHP and I have successfully ported it to Java.  I have broken the solution into multiple steps.

Firstly we identify if the user has presented the email using commonly accepted characters.  I would like to point out at this stage I am aware that users are permitted to use more than alpha-numeric characters in user element of email addresses and I am sure you will see that I have tried to be as all encompassing as possible. 

public void validateEmail(String email){
	// string character check
	if(! email.matches("[^@]+@([-\\p{Alnum}]+\\.)*\\p{Alnum}+"))
		throw new Exception("please check you are using only valid characters in the email address entered.");

Secondly before continuing I check to see if the email address is with one of the many well known email providers;  gmail, hotmail or your company etc.  The purpose of this is to skip the DNS lookups for these email addresses.  This might seem like a cop-out but there is a reason for this, these major email provides have catch all mail handlers.  Essentially we know gmail.com exists and we know it will accept emails for any address, so the question remains why do the DNS lookup.  If this is internal you may wish to skip the lookup for local addresses, the end result is less resource use.

String[] temp = request.getParameter("email").split("@");
	user = temp[0];	
	hostname = temp[1];
 
	// is it one of the common domains. 
	String [] hosts = {"tomred.net","gmail.com","hotmail.com","googlemail.com","yahoo.com"};
	for (String host : hosts)  
		if(hostname.trim().equalsIgnoreCase(host))
			return;	// if the domain is in the host list then finish

Now that we know it is not in the common list we must begin the DNS record lookup for MX records and if that fails we will look for A records.  The A record lookup is not optional but does help in cases where sub domains don't have a dedicated MX but hand-off to the main site e.g. test.tomred.net could be just passing off the mail to tomred.net.  Whether of not to accept A records.  It is worth identifying these anyway for your own information.

// now that it is not a common domain
	Hashtable env = new Hashtable();
	env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
	DirContext ictx = new InitialDirContext( env );
	Attributes attrs = ictx.getAttributes( hostname, new String[] {"MX","A"});
	//printAttrs(attrs);
	Attribute attr = attrs.get( type );
	if( attr == null ){
		throw new Exception("This domain does not exist.");
	}else{
		return;// we have found records we are happy.
	}
}

In the case above you will notice that a String array is passed containing the values "MX" and "A".  This is how we specify which record types we want to test for, since this is an MX lookup we should at least have MX in there.  In this example if no exceptions are thrown we assume all is well.

It is also useful for your testing and general information to print the values from the DNS lookup. I have included //printAttrs(attrs) in the example above if this was uncommented then you have the results of the request output. You could handle these how ever you like. The method to handle this output is described below.

private void printAttrs(Attributes attrs){
        try {
		NamingEnumeration namens = attrs.getAll();
       		while(namens.hasMore()){
			NamingEnumeration namen = namens.next().getAll();
			while(namen.hasMore()){
				System.out.println("Attribute -> "+namen.next().toString());
       			}
       	     	} 
        } catch (NamingException e) {
		e.printStackTrace();
	}
}

I throw exceptions when the email is invalid instead of returning a Boolean [true|false] this is a personal choice and you can use booleans if you wish.  I believe that using exceptions means we can identify where the validation fails instead of passing a blanket rejection.  It would also enable you to internationalise you error text.  If you were to use boolean then the method would be better named isEmailValid.

Comments (0)






Allowed tags: <b><i><br>Add a new comment: