Skip to content

Latest commit

 

History

591 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JMail

Maven Central Javadoc Coverage Status Monthly Downloads

A modern, fast, zero-dependency library for parsing, validating, and working with email addresses in Java.

Built for Java 8 and up.

Try out the algorithm online!

Why JMail?InstallationPerformanceUsageIP ValidationContributing

Why JMail?

JMail was built mainly because I wanted to tackle the complex problem of email address validation without using Regex. Along the way, JMail became a much better choice than other Java email validation libraries (such as Apache Commons Validator, Jakarta Mail Validation, or Google dot-parse) for the following reasons:

  1. JMail is more correct than other libraries. For example, both Apache Commons and Jakarta Mail consider first@last@test.org as a valid email address! It clearly is not, as it has two @ characters. JMail correctly considers this address invalid. No other library does better than 78% correct (JMail is 100%)! You can see a full comparison of correctness and try it out for yourself online.

  2. JMail is faster than other libraries by, on average, at least 2x, thanks in part to lack of regex. See the performance section for more details.

  3. JMail has zero dependencies and is very lightweight.

  4. JMail is customizable and feature-rich. You can provide custom validation rules, directly access different parts of the email addresses, and transform addresses into a wide variety of canonical formats.

  5. JMail is modern. It is built for Java 8+, and provides many useful methods, data accessors, and canonical address formats.

Click here for a full report of the differences in correctness and speed between JMail and other libraries.

While JMail is more correct than other libraries, I cannot guarantee that it is 100% correct. Email RFCs are long and complex, and I have likely missed some specific details. Please open an issue if you find an incorrect validation result for a specific email (or even better, a pull request with a fix).

I also highly recommend that you send verification emails to user-provided email addresses. This is the only way to ensure that the email address exists and that the recipient wants that email address to be used.

Installation

Add this library as a dependency in your pom.xml:

<dependency>
  <groupId>com.sanctionco.jmail</groupId>
  <artifactId>jmail</artifactId>
  <version>2.2.2</version>
</dependency>

Or in your build.gradle:

implementation 'com.sanctionco.jmail:jmail:2.2.2'

Performance

Average validation time in nanoseconds per operation (ns/op), measured with JMH.

JMail can take slightly longer (tens of nanoseconds) than Jakarta for some valid address because of the comprehensivity of its checks. Note that Jakarta Mail has the worst correctness result of all competing libraries (61.5% correct). The main loop in JMail ensures full RFC correctness and so requires more and branches in the logic than any implementation that reduces the RFC set they validate against. Even so, invalid addresses complete much quicker than Jakarta Mail. Despite all the extra logic, JMail has fantastic performance numbers across the board, beating all other implementations, and doesn't give up anything in correctness. No matter what address you give JMail, time only increases based on the length or complexity of the address, making it truly an O(n) solution.

Note that some implementations throw exceptions on invalid addresses, which does impact their performance results since exception handling is expensive in Java. This is still an accurate representation since these implementations do not provide an API to validate without exceptions.

This consistent performance, combined with much better correctness, a richer API set, and more out-of-the-box customization and canonicalization options, makes JMail the best choice for email address parsing and validation.

Email address JMail Jakarta Mail Google dot-parse Apache Commons email-rfc2822
email@example.com 98 71 167 678 2034
first.middle.last@sub.division.example.co.uk 258 175 297 1573 5129
user@münchen.de 647 63 663 1331 2229
user@12345.example.com 168 86 215 776 1821
first@last@example.org (Invalid) 35 815 1393 411 2867
valid.local@exam_ple.com (Invalid) 59 835 1421 881 3384
"john doe"(a comment)@example.com (Invalid) 348 501 1508 719 8433

List Parsing

JMail address list parsing (parsing addresses from a comma-separated String) is also faster than the competition.

Address list JMail Google dot-parse
3 valid addresses email@example.com,test@gmail.com,my-addr@test.org 450 786
2 addresses, one with display name Joe A Smith <email@example.com>,testmail@t.co 512 545
3 addresses, 2 of them invalid e_mail@hello.net,gatsby@f.sc.ot.t.f.i.tzg.era.l.d.,testmail@t.co 389 1706

Usage

Standard Email Validation

To perform standard, RFC-compliant email validation, you can use the static methods available in JMail. For example, to test validation:

String email = "test@example.com";

if (JMail.isValid(email)) {
  // Work with your email string
}

Or to enforce validation, throwing an InvalidEmailException on failure:

String email = "test@example.com";

try {
  JMail.enforceValid(email);
  
  // Work with your email string
} catch (InvalidEmailException _) {
  // Handle invalid email
}

You can also retrieve the reason for validation failure with the validate method:

String email = "test@example.com"

EmailValidationResult result = JMail.validate(email);

if (result.isSuccess()) {
  // Use the email address
} else {
  FailureReason reason = result.getFailureReason();

  logger.error("Validating email address failed with reason: " + reason);
}

Custom Email Validation

JMail also provides an EmailValidator class that allows for much more customization of what constitutes a valid email address. You can require additional common validation rules, or supply your own. For example:

// In general, you should use JMail.strictValidator()
EmailValidator validator = JMail.strictValidator()
    // Require that the top-level-domain is ".com"
    .requireOnlyTopLevelDomains(TopLevelDomain.DOT_COM)
    // Require that the local-part starts with "allowed"
    .withRule(email -> email.localPart().startsWith("allowed"),
            new FailureReason("DOES_NOT_START_WITH_ALLOWED"));

boolean valid = validator.isValid("allowed-email@test.com");
boolean invalidWithoutTld = validator.isValid("allowed@test");
boolean invalidWithoutDotCom = validator.isValid("allowed@test.net");
boolean invalidWithoutAllowed = validator.isValid("invalid@test.com");

The Email Object

JMail also includes an Email object that makes working with email addresses easier. The Email object has the following properties:

Property getter Description Example using test(hello)@(world)example.one.com
localPart() The local-part of the email address test(hello)
localPartWithoutComments() The local-part of the email address without comments test
domain() The domain of the email address (world)example.one.com
domainWithoutComments() The domain of the email address without comments example.one.com
domainParts() A list of the parts of the domain [example, one, com]
displayName() The display name of the email address, if it has one. Optional.empty
(For Admin <test@server.com>, it would be Admin)
decodedDisplayName() A fully MIME-decoded version of the display name of the email address, if it has one. Optional.empty
(For =?utf-8?q?te?xt?= <test@server.com>, it would be te?xt)
comments() A list of the comments in the email address [hello, world]
explicitSourceRoutes() A list of explicit source routes in the address, if present []
(For @1st.relay,@2nd.relay:user@final.domain, it would be [1st.relay, 2nd.relay])
isIpAddress() Whether the domain is an IP address false
containsWhitespace() Whether the address contains obsolete whitespace false
isAscii() Whether the address contains only ASCII characters true
hasDisplayName() Whether the address has an display name false
topLevelDomain() The TopLevelDomain of the email address, or TopLevelDomain.OTHER if it is unknown TopLevelDomain.DOT_COM

To create a new instance of Email from a string, use the tryParse(String email) method, either the default version or on your own EmailValidator instance:

Optional<Email> parsed = JMail.tryParse("test@example.com");

Optional<Email> parsed = JMail.validator()
    .disallowIpDomain()
    .tryParse("test@example.com");

Since tryParse(String email) returns an Optional<Email>, you can do some cool things, such as:

Use a default email address

String email = JMail.tryParse("invalidEmailString")
    .map(Email::toString)
    .orElse("default@example.com");

Send an email if the address is valid

JMail.tryParse("test@example.com")
    .ifPresentOrElse(
        email -> myEmailService.sendTo(email.toString()),
        () -> log.error("Could not send email to invalid email"));

Get different versions of the email address

// Get a normalized email address
Optional<String> normalized = JMail.tryParse("admin(comment)@mysite.org")
    .map(Email::normalized);

// normalized == Optional.of("admin@mysite.org")
// Get a normalized email address and remove any sub-addressing when normalizing
Optional<String> normalized = JMail.tryParse("test.1+mytag@mysite.org")
        .map(e -> e.normalized(
            NormalizationOptions.builder()
                    .removeSubAddress()
                    .build()));

// normalized == Optional.of("test.1@mysite.org")
// Get a reference (MD5 hash) of the email address
Optional<String> reference = JMail.tryParse("test@gmail.com")
        .map(Email::reference);

// redacted == Optional.of("1aedb8d9dc4751e229a335e371db8058");
// Get a redacted version of the email address
Optional<String> redacted = JMail.tryParse("test@gmail.com")
        .map(Email::redacted);

// redacted == Optional.of("{a94a8fe5ccb19ba61c4c0873d391e987982fbbd3}@gmail.com");
// Get a munged version of the email address
Optional<String> redacted = JMail.tryParse("test@gmail.com")
        .map(Email::munged);

// redacted == Optional.of("te*****@gm*****");

Parsing Lists of Addresses

JMail is capable of parsing a string that contains a list of email addresses delimited by commas.

The address list parsing is intelligent enough to handle commas that appear in one of the addresses in the list in an RFC-valid location, such as quoted in the local-part or within a comment. It does this very efficiently by iterating once through each character in the given list of addresses, avoiding whitespace, and intelligently pulling out each token that should be validated as an address.

Two methods are provided: tryParseAddressList which discards invalid addresses in the list and just returns the valid parsed Email objects, and validateAddressList which returns a list of EmailValidationResults from each possible address in the list that can be inspected for various failures.

List<Email> emails = JMail.tryParseAddressList(
    "good@example.com,not-an-email,also@example.org");
// emails == [Email{"good@example.com"}, Email{"also@example.org"}]

List<Email> emails = JMail.validateAddressList(
    "good@example.com,not-an-email,also@example.org");
// emails == [
//   EmailValidationResult{success=true, emailAddress="good@example.com"},
//   EmailValidationResult{success=false, failureReason="MISSING_AT_SYMBOL"},
//   EmailValidationResult{success=true, emailAddress="also@example.org"}
// ]

Additional Validation Rules

Disallow IP Address Domain

Although an email with an IP address in the domain is valid, these email addresses are often rejected from mail servers or only used for spam. You can require that your EmailValidator reject all emails with an IP address in the domain:

JMail.validator().disallowIpDomain();

Note: JMail.strictValidator() includes this rule automatically.

Require a Top Level Domain

Although an email address can be a local domain name with no TLD, ICANN highly discourages dotless email addresses. You can require that your EmailValidator reject all emails without a TLD:

JMail.validator().requireTopLevelDomain();

Note: JMail.strictValidator() includes this rule automatically.

Disallow Explicit Source Routing

Explicit source routing has been deprecated as of RFC 5321 and you SHOULD NOT use explicit source routing except under unusual circumstances.

JMail.validator().disallowExplicitSourceRouting();

Note: JMail.strictValidator() includes this rule automatically.

Disallow Single Character Top Level Domains

A common user error is single-character top level domains (such as accidentally typing test@test.c instead of test@test.com). These single character TLDs don't actually exist and are not resolvable.

You can require that addresses do not have these single-character TLDs:

JMail.validator().disallowSingleCharacterTopLevelDomains();

Disallow Reserved Domains

As specified in RFC 2606, some domains are reserved and should not be resolvable. Mail addressed to mailboxes in those reserved domains (and their subdomains) should be non-deliverable. You can require that your EmailValidator reject all emails that have a reserved domain:

JMail.validator().disallowReservedDomains();

Disallow Display Names

If you want email addresses to only be the raw email address, use this rule. Adding this will invalidate addresses of the form John Smith <john@smith.com>.

JMail.validator().disallowDisplayNames();

Require a specific common Top Level Domain

You can require that your EmailValidator reject all emails that have a top-level domain other than the ones you specify:

JMail.validator().requireOnlyTopLevelDomains(TopLevelDomain.DOT_COM);
JMail.validator().requireOnlyTopLevelDomains(
    TopLevelDomain.DOT_NET, TopLevelDomain.DOT_EDU);

Disallow Obsolete Whitespace

Whitespace (spaces, newlines, and carriage returns) is by default allowed between dot-separated parts of the local-part and domain since RFC 822. However, this whitespace is considered obsolete since RFC 2822.

You can require that your EmailValidator reject all emails that have obsolete whitespace.

JMail.validator().disallowObsoleteWhitespace();

Require a valid MX record

You can require that your EmailValidator reject all email addresses that do not have a valid MX record associated with the domain.

Please note that including this rule on your email validator can increase the amount of time it takes to validate email addresses by approximately 600ms in the worst case. To further control the amount of time spent doing DNS lookups, you can use the overloaded method to customize the timeout and retries.

JMail.validator().requireValidMXRecord();

// Or, customize the timeout and retries
JMail.validator().requireValidMXRecord(50, 2);

Disallow Disposable Domains

There are many services that provide disposable (or temporary) email addresses. Many applications want to block these email addresses since they tend to be used by bots or users who are not serious about signing up.

You can require that your EmailValidator reject all email addresses that have a disposable domain.

You must provide a DisposableDomainSource that is able to determine what domains are considered disposable.

Currently, there are three provided DisposableDomainSource implementations:

  1. DisposableDomainSource.inputStream(InputStream stream) uses a given InputStream as the source of truth for disposable domains.
  2. DisposableDomainSource.file(String path) uses a provided file containing disposable domains as the source. The file could be taken from disposable-email-domains for example, and included in your application.
  3. DisposableDomainSource.isTempMailAPI(String apiKey) uses the IsTempMail API to determine which domains are disposable. To use this source, you must sign up with IsTempMail and get an API key for usage.

Please note that using the IsTempMailAPI source for this rule on your email validator can increase the amount of time it takes to validate email addresses. This is due to the time taken to make the API requests.

// Using a FileSource
DisposableDomainSource fileSource = DisposableDomainSource.file("path/to/my/file.txt");
JMail.validator().disallowDisposableDomains(fileSource);

// Using a IsTempMailAPISource
DisposableDomainSource apiSource = DisposableDomainSource.isTempMailAPI("MY_API_KEY");
JMail.validator().disallowDisposableDomains(apiSource);

Require the address to be ASCII

Some older email servers cannot yet accept non-ASCII email addresses. You can require that your EmailValidator reject all email addresses that contain characters other than ASCII characters.

JMail.validator().requireAscii();

Allow Nonstandard Dots in the Local-Part

While technically disallowed under published RFCs, some email providers (ex: GMail) consider email addresses that have local-parts that start with or end with a dot . character as valid. For example, GMail considers .my.email.@gmail.com valid, even though it is not actually valid according to RFC.

JMail.validator().allowNonstandardDots();

Bonus: IP Address Validation

Since validating email addresses requires validation of IP addresses, these IP address validation methods are exposed for your convenience!

Determine if an IP Address is Valid

String ipv4 = "12.34.56.78";

if (InternetProtocolAddress.isValid(ipv4)) {
  // Use address
}
String ipv6 = "2001:db8::1234:5678";

if (InternetProtocolAddress.isValid(ipv6)) {
  // Use address
}

Enforce an IP Address to be Valid

String ipv4 = "12.34.56.78";

try {
  InternetProtocolAddress.enforceValid(ipv4);
} catch (InvalidAddressException e) {
  // Failure
}
String ipv6 = "2001:db8::1234:5678";

try {
  InternetProtocolAddress.enforceValid(ipv6);
} catch (InvalidAddressException e) {
  // Failure
}

Validate and return the IP

String ipv4 = "12.34.56.78";

Optional<String> validated = InternetProtocolAddress.validate(ipv4);

// The validate() method allows for convenience such as:
String ip = InternetProtocolAddress
        .validate("notvalid")
        .orElse("0.0.0.0");
String ipv6 = "2001:db8::1234:5678";

Optional<String> validated = InternetProtocolAddress.validate(ipv6);

// The validate() method allows for convenience such as:
String ip = InternetProtocolAddress
        .validate("notvalid")
        .orElse("2001:db8::1234:5678");

Contributing

All contributions are welcome! Open issues for bug reports or feature requests. Pull requests with fixes or enhancements are encouraged.

Relevant RFCs: 822, 2047, 2822, 5321, 5322, 6531

About

A modern and lightweight library for working with email addresses in Java

Topics

Resources

Stars

192 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages