Record Class EmailAddress

java.lang.Object
java.lang.Record
com.google.common.labs.email.EmailAddress
Record Components:
localPart - the "tolkien" from J.R.R. Tolkien <tolkien@lotr.org>
domain - the "lotr.org" from J.R.R. Tolkien <tolkien@lotr.org>. Note that for internationalized domain, this is the punycode in ASCII. You can use unicodeDomain() to access the non-encoded domain. hasI18nDomain() can be used to check if the domain is internationalized.
displayName - the "J.R.R. Tolkien" from J.R.R. Tolkien <tolkien@lotr.org>. Note that this holds the raw transport-safe format (including any RFC 2047 encoded-words). You can use unicodeDisplayName() to access the decoded Unicode representation.

@Immutable @CheckReturnValue public record EmailAddress(String localPart, String domain, Optional<String> displayName) extends Record
Represents a strictly validated email address according to RFC 5322, designed as a modern, light-weight alternative API to javax.mail.InternetAddress.

See README for a comprehensive architectural, security, and performance comparison against Jakarta Mail and JMail.

For example:


 EmailAddress address = EmailAddress.of("J.R.R. Tolkien <tolkien@lotr.org>");
 // address.displayName() => "J.R.R. Tolkien"
 // address.localPart() => "tolkien"
 // address.domain() => "lotr.org"
 

RFC 5322 Compliance Profile

  • Address Specification (addr-spec): Supports the standard local-part@domain format (RFC 5322 §3.4.1).
  • Quoted Local-Parts: Fully supports double-quoted local-parts (RFC 5322 §3.4.1), with backslash-escaped characters. In order to provide a clean, canonical representation, enclosing quotes are automatically stripped and backslash escapes are unescaped when stored in the localPart property (e.g., "john doe" -> "john doe"). While serializing via address() or toString(), appropriate quotes and escapes are dynamically and safely re-introduced if necessary to maintain syntactical validity under RFC 5322 (since v10.3).
  • Name-Addr: Fully supports "display-name" <addr-spec> syntax (RFC 5322 §3.4).
  • Quoted-Strings: Complies with RFC 5322 §3.2.4, supporting backslash-escaped characters within double-quoted display names.
  • Phrases (unquoted names): Supports RFC 5322 "atoms" in display names, forbidding specials, i.e. the <, >, ;, \, and " characters, while allowing periods, commas, colons, brackets, and parentheses for real-world usability (e.g., "[JIRA] (PROJ-123)").
  • Folding White Space (FWS): Supports optional whitespace between the display name and the angle-bracketed address.
  • Address-List: Supports semicolon as separators; allows real-world variations like trailing commas, two-commas-in-a-row etc.

Strict Email Address Validation

Unlike legacy javax.mail.InternetAddress from Java/Jakarta Mail, this class is backed by a strict email address parser and a validated domain model. This improves security, preventing common email parsing exploits such as:

  • Email Parsing Differentials: Inputs like <legitimate@trusted.com>attacker@evil.com are strictly rejected instead of being silently truncated or partially parsed, preventing privilege escalation or routing bypasses.
  • Display Name Spoofing (RFC 2047): Display names are preserved literally in their raw/encoded form by default (preventing visual spoofing/phishing side-channels). Safe opt-in decoding is provided explicitly via unicodeDisplayName().
  • Group Addresses and Multi-@ Local-Parts: Group address syntaxes and unquoted multi-@ injections are strictly disallowed.

Comparison with javax.mail.InternetAddress (Java / Jakarta Mail)

Feature/Security Aspect InternetAddress EmailAddress
Immutability Mutable POJO Immutable record
DNS labels Permissive (allows illegal hyphens) Rejects invalid/misplaced hyphens
I18n Often requires Encoded-Words Native BMP support
Parsing Differential Vulnerable (silently discards trailing parts like <a@b.com>c@d.com) Secure (strictly rejects trailing unconsumed text via parser exceptions)
Group Addresses Permissive (parses RFC-822 group syntax implicitly) Strictly rejected (enforces single address structure)
RFC 2047 Encoded Words Automatic or permissive (decodes or accepts encoded words in display name, local-part, or domain, risking address spoofing and routing hijacking) Defensively rejected in local-part and domain. Supported in display name via safe, explicit opt-in unicodeDisplayName()
Multi-@ Local-Parts Inconsistent (allows unquoted @ in local-part) Strictly rejected (unquoted @ is forbidden)

Intentionally Omitted Legacy Features

To maintain compatibility with modern MTAs (Gmail, Outlook) and mitigate header injection risks, the following RFC 5322 edge cases are excluded:

  • Comments (CFWS): (e.g., name(comment) <addr>) - De facto obsolete.
  • Domain Literals: (e.g., user@[192.168.1.1]) - IP routing is rarely supported.
  • RFC 2047 Encoded Words in address fields: (e.g., =?UTF-8?Q?Admin?=@domain.com) - Strictly rejected to prevent downstream mailer decoding exploits.
Since:
9.9.4
  • Field Details

    • PARSER

      public static final Parser<EmailAddress> PARSER
      The parser for email address, according to RFC 5322, and supporting BMP characters.

      Prefer using the of(java.lang.String, java.lang.String) convenience method. This constant is to be used for composition, for example to parse a group addresses:

      
       Parser.sequence(
           Parser.word().followedBy(":"),
           EmailAddress.PARSER.zeroOrMoreDelimitedBy(",").followedBy(";"),
           GroupAddress::new);
       
  • Constructor Details

  • Method Details

    • withDisplayName

      public EmailAddress withDisplayName(String displayName)
      Returns an otherwise equivalent EmailAddress but with displayName.
    • unicodeDisplayName

      public Optional<String> unicodeDisplayName()
      Returns the display name in Unicode (with any RFC 2047 encoded-words decoded), or Optional.empty() if no display name is present.

      To prevent visual spoofing and phishing attacks in standard rendering, the main displayName() component is kept in its raw transport-safe encoded form. This method provides a safe, explicit opt-in to decode the display name.

      Only standard, ASCII-compatible charsets (specifically UTF-8, ISO-8859-1, and US-ASCII) are decoded, guarding against null-byte injection exploits in downstream systems. Unsupported charsets or syntactically malformed encoded-words are safely left in their encoded form.

      Since:
      10.3.1
    • of

      public static EmailAddress of(String localPart, String domain)
      For example: EmailAddress.of("user", "mycompany.com").
    • of

      public static EmailAddress of(String address)
      Parses address and throws Parser.ParseException if failed.
      Since:
      9.9.8
    • address

      public String address()
      Returns the addr-spec, in the form of user@mycompany.com.
    • user

      public String user()
      Returns the "user" part of the local-part before the first + separator, according to RFC 5233 subaddressing. If no + is present, the full localPart is returned.
      Since:
      10.3
    • alias

      public Optional<String> alias()
      Returns the "alias" (or "detail") part of the local-part after the first + separator, according to RFC 5233 subaddressing. If no + is present, or if the + is the last character of the local-part, Optional.empty() is returned.
      Since:
      10.3
    • unicodeDomain

      public String unicodeDomain()
      Returns the domain in Unicode.
      Since:
      10.3
    • hasI18nDomain

      public boolean hasI18nDomain()
      Returns true if this address has an internationalized domain, in which case domain() will be puny-coded.
      Since:
      10.3
    • toString

      public String toString()
      Returns the full email address, in the form of local-part@domain or "display name" <local-part@domain>. Backslashes and double quotes in the display name are auto-escaped.
      Specified by:
      toString in class Record
    • parse

      @Deprecated @InlineMe(replacement="EmailAddress.of(address)", imports="com.google.common.labs.email.EmailAddress") public static EmailAddress parse(String address)
      Deprecated.
      Use of(String) instead
    • parseAddressList

      public static List<EmailAddress> parseAddressList(String addressList)
      Parses addressList according to RFC 5322 and returns an immutable list of EmailAddress.

      Both comma (,) and semicolon (;) are supported as delimiters, with whitespaces ignored. Trailing delimiters are allowed.

      Empty input will result in an empty list being returned.

      Note that if your address list may contain invalid entries, and you'd want to ignore them instead of failing, use parseAddressList(String, Consumer).

      Throws:
      Parser.ParseException - if addressList is invalid
    • parseAddressList

      public static List<EmailAddress> parseAddressList(String addressList, Consumer<? super String> ifInvalid)
      Parses addressList according to RFC 5322 and returns an immutable list of EmailAddress, with invalid entries passed to the ifInvalid consumer.

      For example,

      
       List<EmailAddress> addresses = parseAddressList(inputAddressList, logger::log);
       

      Both comma (,) and semicolon (;) are supported as delimiters, with whitespaces ignored. Trailing delimiters are allowed.

      Empty input will result in an empty list being returned.

      Since:
      10.3
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. All components in this record class are compared with Objects::equals(Object,Object).
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • localPart

      public String localPart()
      Returns the value of the localPart record component.
      Returns:
      the value of the localPart record component
    • domain

      public String domain()
      Returns the value of the domain record component.
      Returns:
      the value of the domain record component
    • displayName

      public Optional<String> displayName()
      Returns the value of the displayName record component.
      Returns:
      the value of the displayName record component